-
I have a global query scope which is very similar to the Any Tipps how I could type add support for my custom scope? <?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class ExpirationScope implements Scope
{
/**
* All of the extensions to be added to the builder.
*
* @var string[]
*/
protected $extensions = [ 'WithExpired', 'WithoutExpired'];
/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
*/
public function apply(Builder $builder, Model $model)
{
$builder->where(function (Builder $builder) use ($model) {
$column = $model->getQualifiedExpiresAtColumn();
$time = $model->freshTimestamp();
return $builder
->whereNull($column)
->orWhere($column, '>', $time);
});
}
/**
* Extend the query builder with the needed functions.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
*/
public function extend(Builder $builder)
{
foreach ($this->extensions as $extension) {
$this->{"add{$extension}"}($builder);
}
}
/**
* Add the with-expirted extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
*/
protected function addWithExpired(Builder $builder)
{
$builder->macro('withExpired', function (Builder $builder, $withExpired = true) {
if (! $withExpired) {
return $builder->withoutExpired();
}
return $builder->withoutGlobalScope($this);
});
}
/**
* Add the without-expired extension to the builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
*/
protected function addWithoutExpired(Builder $builder)
{
$builder->macro('withoutExpired', function (Builder $builder) {
return $builder
->withoutGlobalScope($this)
->where(function (Builder $builder) {
$model = $builder->getModel();
$column = $model->getQualifiedExpiresAtColumn();
$time = $model->freshTimestamp();
return $builder
->whereNull($column)
->orWhere($column, '>', $time);
});
});
}
} ------ ------------------------------------------------------------------------------------------------
Line app/Models/Scopes/ExpirationScope.php
------ ------------------------------------------------------------------------------------------------
27 Call to an undefined method Illuminate\Database\Eloquent\Model::getQualifiedExpiresAtColumn().
57 Call to an undefined method Illuminate\Database\Eloquent\Builder::withoutExpired().
77 Call to an undefined method Illuminate\Database\Eloquent\Model::getQualifiedExpiresAtColumn().
------ ------------------------------------------------------------------------------------------------ The corresponding Models use a Trait which is similar to the /**
* @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withExpired(bool $withExpired = true)
* @method static \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutExpired()
*/
trait Expirable
{
/**
* Boot the trait for a model.
*
* @return void
*/
public static function bootExpirable(): void
{
static::addGlobalScope(new ExpirationScope);
}
/**
* Initialize the trait for an instance.
*
* @return void
*/
public function initializeExpirable(): void
{
if (! isset($this->casts[$this->getExpiresAtColumn()])) {
$this->casts[$this->getExpiresAtColumn()] = 'datetime';
}
}
/**
* An alias for {@see Expirable::expired()}.
*
* @return bool
*/
public function isExpired(): bool
{
return $this->expired();
}
/**
* Determine if the model instance is expired.
*
* @return bool
*/
public function expired(): bool
{
$value = $this->{$this->getExpiresAtColumn()};
if (empty($value)) {
return false;
}
return ! $this->asDateTime($value)->isFuture();
}
/**
* Get the name of the "expired at" column.
*
* @return string
*/
public function getExpiresAtColumn(): string
{
/** @phpstan-ignore-next-line */
return defined('static::EXPIRES_AT') ? static::EXPIRES_AT : 'expires_at';
}
/**
* Get the fully qualified "expired at" column.
*
* @return string
*/
public function getQualifiedExpiresAtColumn(): string
{
return $this->qualifyColumn($this->getExpiresAtColumn());
}
/**
* Set the value of the "expired at" attribute.
*
* @param mixed $value
* @return $this
*/
public function setExpiresAt($value): Model
{
$this->{$this->getExpiresAtColumn()} = $value;
return $this;
}
// […]
} |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
Hi, The errors you show are from the scope class itself, not from anywhere that the scope is used. So I think it's safe to ignore errors coming from the scope itself. Fixing those can be tricky because you'd need to provide the real models that using this scope, in the PHPDocs. Global scopes are not directly supported by Larastan and the only way to add support is like you did with adding those PHPDocs to the trait. And I think better docs would be something like this: /**
* @method static \Illuminate\Database\Eloquent\Builder<static> withExpired(bool $withExpired = true)
* @method static \Illuminate\Database\Eloquent\Builder<static> withoutExpired()
*/ |
Beta Was this translation helpful? Give feedback.
-
I'm trying to follow this with what I think is a similar problem, but having no luck to get a recognition of the scope that lives inside a trait. I have the following trait, and every model that implements this trait throws a PHPStan/LaraStan error <?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
/**
* @property Team $team
*
* @method static Builder<static> team()
*/
trait BelongsToTeam
{
public static function bootBelongsToTeam()
{
static::creating(function ($model) {
if (auth()->check()) {
$model->team_id = auth()->user()->currentTeam->getKey();
}
});
}
protected static function booted(): void
{
parent::booted();
if (auth()->check()) {
static::addGlobalScope('team', function (Builder $query) {
$query->team();
});
}
}
public function scopeTeam(Builder $query)
{
if (auth()->check()) {
$query->where('team_id', auth()->user()->currentTeam->getKey());
}
}
public function team()
{
return $this->belongsTo(Team::class);
}
} |
Beta Was this translation helpful? Give feedback.
-
That did it! Thanks, not sure I would have figured that out, but makes sense now that I see it. |
Beta Was this translation helpful? Give feedback.
Hi,
The errors you show are from the scope class itself, not from anywhere that the scope is used. So I think it's safe to ignore errors coming from the scope itself. Fixing those can be tricky because you'd need to provide the real models that using this scope, in the PHPDocs.
Call to an undefined method Illuminate\Database\Eloquent\Model::getQualifiedExpiresAtColumn().
happens because in fact that method does not exists inIlluminate\Database\Eloquent\Model
But for example it might exist inApp\Models\Foo
Global scopes are not directly supported by Larastan and the only way to add support is like you did with adding those PHPDocs to the trait. And I think better docs would be something …