diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9787b631..9312cf75 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,7 +7,8 @@ parameters: paths: - src/ - config/ - - database/ +# TODO +# - database/ - routes/ level: 5 checkAlwaysTrueCheckTypeFunctionCall: true @@ -23,8 +24,11 @@ parameters: checkInternalClassCaseSensitivity: true ignoreErrors: - '#^Unsafe usage of new static#' + # --- TODO-s --- # Tricky readonlys - '#Assign it in the constructor\.$#' - '#is assigned outside of the constructor\.$#' - # View vs. View contract - - '#render\(\) should return Illuminate\\View\\View but#' + # X vs. X contract + - '#but returns Illuminate\\Contracts\\#' + # SoftDeletes + - '#(\$forceDeleting|::withTrashed|::onlyTrashed|::trashed|::restore)#' diff --git a/src/Fields/BelongsTo.php b/src/Fields/BelongsTo.php index 4fd871e0..c247595e 100644 --- a/src/Fields/BelongsTo.php +++ b/src/Fields/BelongsTo.php @@ -6,6 +6,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo as EloquentRelation; use Illuminate\Http\Request; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\BelongsTo + * + * @extends \Cone\Root\Fields\Relation + */ class BelongsTo extends Relation { /** diff --git a/src/Fields/BelongsToMany.php b/src/Fields/BelongsToMany.php index a87749eb..a3fbf2e2 100644 --- a/src/Fields/BelongsToMany.php +++ b/src/Fields/BelongsToMany.php @@ -9,6 +9,11 @@ use Illuminate\Http\Request; use Illuminate\Support\Arr; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\BelongsToMany + * + * @extends \Cone\Root\Fields\Relation + */ class BelongsToMany extends Relation { use ResolvesFields; diff --git a/src/Fields/Fieldset.php b/src/Fields/Fieldset.php index 59b105f5..141d9cf3 100644 --- a/src/Fields/Fieldset.php +++ b/src/Fields/Fieldset.php @@ -77,7 +77,7 @@ public function toValidate(Request $request, Model $model): array { return array_merge( parent::toValidate($request, $model), - $this->resolveFields($request)->mapToValidate($request) + $this->resolveFields($request)->mapToValidate($request, $model) ); } } diff --git a/src/Fields/HasMany.php b/src/Fields/HasMany.php index 1ff45280..8ac39e6a 100644 --- a/src/Fields/HasMany.php +++ b/src/Fields/HasMany.php @@ -6,6 +6,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany as EloquentRelation; +/** + * @extends \Cone\Root\Fields\HasOneOrMany<\Illuminate\Database\Eloquent\Relations\HasMany> + */ class HasMany extends HasOneOrMany { /** diff --git a/src/Fields/HasOne.php b/src/Fields/HasOne.php index cd071702..e6bda9b2 100644 --- a/src/Fields/HasOne.php +++ b/src/Fields/HasOne.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne as EloquentRelation; +/** + * @extends \Cone\Root\Fields\HasOneOrMany<\Illuminate\Database\Eloquent\Relations\HasOne> + */ class HasOne extends HasOneOrMany { /** diff --git a/src/Fields/HasOneOrMany.php b/src/Fields/HasOneOrMany.php index 01a6c894..f58d318b 100644 --- a/src/Fields/HasOneOrMany.php +++ b/src/Fields/HasOneOrMany.php @@ -7,6 +7,11 @@ use Illuminate\Http\Request; use Illuminate\Support\Arr; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\HasOneOrMany + * + * @extends \Cone\Root\Fields\Relation + */ abstract class HasOneOrMany extends Relation { /** diff --git a/src/Fields/Meta.php b/src/Fields/Meta.php index 37e72eec..83339a9a 100644 --- a/src/Fields/Meta.php +++ b/src/Fields/Meta.php @@ -9,6 +9,9 @@ use Illuminate\Database\Eloquent\Relations\MorphOne as EloquentRelation; use Illuminate\Http\Request; +/** + * @extends \Cone\Root\Fields\MorphOne<\Illuminate\Database\Eloquent\Relations\MorphOne> + */ class Meta extends MorphOne { /** diff --git a/src/Fields/MorphMany.php b/src/Fields/MorphMany.php index 2fa0385e..7da69d4e 100644 --- a/src/Fields/MorphMany.php +++ b/src/Fields/MorphMany.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphMany as EloquentRelation; +/** + * @extends \Cone\Root\Fields\MorphOneOrMany<\Illuminate\Database\Eloquent\Relations\MorphMany> + */ class MorphMany extends MorphOneOrMany { /** diff --git a/src/Fields/MorphOne.php b/src/Fields/MorphOne.php index d17f7ace..e2776f77 100644 --- a/src/Fields/MorphOne.php +++ b/src/Fields/MorphOne.php @@ -5,6 +5,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphOne as EloquentRelation; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\MorphOne + * + * @extends \Cone\Root\Fields\MorphOneOrMany + */ class MorphOne extends MorphOneOrMany { /** diff --git a/src/Fields/MorphOneOrMany.php b/src/Fields/MorphOneOrMany.php index ab928ed5..9ad8d3c4 100644 --- a/src/Fields/MorphOneOrMany.php +++ b/src/Fields/MorphOneOrMany.php @@ -5,6 +5,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphOneOrMany as EloquentRelation; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\MorphOneOrMany + * + * @extends \Cone\Root\Fields\HasOneOrMany + */ abstract class MorphOneOrMany extends HasOneOrMany { /** diff --git a/src/Fields/MorphTo.php b/src/Fields/MorphTo.php index 36a155e6..afb094bb 100644 --- a/src/Fields/MorphTo.php +++ b/src/Fields/MorphTo.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo as EloquentRelation; +/** + * @extends \Cone\Root\Fields\BelongsTo<\Illuminate\Database\Eloquent\Relations\MorphTo> + */ class MorphTo extends BelongsTo { /** diff --git a/src/Fields/MorphToMany.php b/src/Fields/MorphToMany.php index 8e358143..5c98ffa2 100644 --- a/src/Fields/MorphToMany.php +++ b/src/Fields/MorphToMany.php @@ -5,6 +5,9 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphToMany as EloquentRelation; +/** + * @extends \Cone\Root\Fields\BelongsToMany<\Illuminate\Database\Eloquent\Relations\MorphToMany> + */ class MorphToMany extends BelongsToMany { /** diff --git a/src/Fields/Relation.php b/src/Fields/Relation.php index 3bea0069..fdd80a23 100644 --- a/src/Fields/Relation.php +++ b/src/Fields/Relation.php @@ -10,6 +10,9 @@ use Illuminate\Support\Collection; use Illuminate\Support\Str; +/** + * @template TRelation of \Illuminate\Database\Eloquent\Relations\Relation + */ abstract class Relation extends Field { /** @@ -72,6 +75,8 @@ public static function scopeQuery(Closure $callback): void /** * Get the relation instance. + * + * @phpstan-return TRelation */ public function getRelation(Model $model): EloquentRelation { diff --git a/src/Support/Slug.php b/src/Support/Slug.php new file mode 100644 index 00000000..7e9822ba --- /dev/null +++ b/src/Support/Slug.php @@ -0,0 +1,170 @@ +model = $model; + } + + /** + * Set the "from" property. + */ + public function from(array|string $attributes): static + { + $this->from = (array) $attributes; + + return $this; + } + + /** + * Set the "to" property + */ + public function to(string $attribute): static + { + $this->to = $attribute; + + return $this; + } + + /** + * Set the "unique" property. + */ + public function unique(bool $value = true): static + { + $this->unique = $value; + + return $this; + } + + /** + * Set the "fresh" property. + */ + public function fresh(bool $value = true): static + { + $this->fresh = $value; + + return $this; + } + + /** + * Set the "resolver" property. + */ + public function generateUsing(Closure $callback): static + { + $this->resolver = $callback; + + return $this; + } + + /** + * Generate the slug. + */ + public function generate(): string + { + $this->from ??= ['id']; + $this->to ??= 'slug'; + $this->separator ??= '-'; + $this->unique ??= false; + $this->fresh ??= false; + + if (! is_null($this->model->getAttribute($this->to)) && ! $this->fresh) { + return $this->model->getAttribute($this->to); + } + + $value = Str::of(implode($this->separator, $this->model->only($this->from))) + ->slug($this->separator) + ->value(); + + if (! is_null($this->resolver)) { + return call_user_func_array($this->resolver, [$this, $value]); + } + + if (! $this->unique) { + return $value; + } + + $match = $this->model + ->newQuery() + ->when( + in_array(SoftDeletes::class, class_uses_recursive($this->model)), + static function (Builder $query): Builder { + return $query->withTrashed(); + } + ) + ->whereRaw(sprintf( + "`%s` regexp '^%s(%s[\\\\d]+)?$'", + $this->to, + preg_quote($value), + preg_quote($this->separator) + )) + ->orderByDesc($this->to) + ->limit(1) + ->value($this->to); + + $value = is_null($match) ? $value : preg_replace_callback( + sprintf('/%s([\d]+)?$/', preg_quote($this->separator)), + static function (array $match): string { + return str_replace($match[1], (string) (((int) $match[1]) + 1), $match[0]); + }, + $match + ); + + return $value === $match ? sprintf('%s%s1', $value, $this->separator) : $value; + } + + /** + * Get the string representation of the slug. + */ + public function __toString(): string + { + return $this->generate(); + } +}