From a9dddb9e49fd8617cf46450f74716fcf213cfb13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 02:54:20 +0000 Subject: [PATCH 1/7] Rebase --- .github/workflows/back-end.yml | 5 +- phpstan.neon.dist | 22 ++++- src/Fields/BelongsTo.php | 5 + src/Fields/BelongsToMany.php | 5 + src/Fields/HasMany.php | 3 + src/Fields/HasOne.php | 3 + src/Fields/HasOneOrMany.php | 5 + src/Fields/Meta.php | 3 + src/Fields/MorphMany.php | 3 + src/Fields/MorphOne.php | 5 + src/Fields/MorphOneOrMany.php | 5 + src/Fields/MorphTo.php | 3 + src/Fields/MorphToMany.php | 3 + src/Fields/Relation.php | 5 + src/Models/Medium.php | 2 + src/Navigation/Item.php | 6 ++ src/Support/Slug.php | 170 +++++++++++++++++++++++++++++++++ src/Widgets/Widgets.php | 3 + 18 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 src/Support/Slug.php diff --git a/.github/workflows/back-end.yml b/.github/workflows/back-end.yml index bbf0080db..e3f432763 100644 --- a/.github/workflows/back-end.yml +++ b/.github/workflows/back-end.yml @@ -135,9 +135,12 @@ jobs: name: "Search for $this->$this typo 🐌" run: | ! git grep --line-number -e '\$this\s*->\s*\$this' -- ':!:*/back-end\.yml' + - + name: "Install orchestra/testbench" + run: "composer require --dev orchestra/testbench" - name: "Perform static analysis" - run: "true TODO || composer exec -- phpstan analyze --level=5 src/" + run: "composer exec -- phpstan || true 'Annotate only'" coding_standards: name: "4️⃣ Coding Standards" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 96ad334f8..9312cf752 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,8 +7,28 @@ parameters: paths: - src/ - config/ - - database/ +# TODO +# - database/ - routes/ level: 5 + checkAlwaysTrueCheckTypeFunctionCall: true + checkAlwaysTrueInstanceof: true + checkAlwaysTrueStrictComparison: true + checkAlwaysTrueLooseComparison: true + checkClassCaseSensitivity: false + checkDynamicProperties: true + checkExplicitMixed: false + checkImplicitMixed: false + checkExplicitMixedMissingReturn: true + checkFunctionNameCase: true + checkInternalClassCaseSensitivity: true ignoreErrors: - '#^Unsafe usage of new static#' + # --- TODO-s --- + # Tricky readonlys + - '#Assign it in the constructor\.$#' + - '#is assigned outside of the constructor\.$#' + # 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 4fd871e0e..c247595eb 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 a87749eb6..a3fbf2e27 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/HasMany.php b/src/Fields/HasMany.php index 1ff452803..8ac39e6a3 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 cd071702f..e6bda9b29 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 01a6c894a..f58d318b1 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 37e72eecf..83339a9a4 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 2fa0385e2..7da69d4e3 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 d17f7ace5..e2776f772 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 ab928ed59..9ad8d3c48 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 36a155e6d..afb094bb9 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 8e3581438..5c98ffa25 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 3bea00693..fdd80a23a 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/Models/Medium.php b/src/Models/Medium.php index f1f392db2..681b04ecb 100644 --- a/src/Models/Medium.php +++ b/src/Models/Medium.php @@ -152,6 +152,8 @@ public function user(): BelongsTo /** * Determine if the file is image. + * + * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function isImage(): Attribute { diff --git a/src/Navigation/Item.php b/src/Navigation/Item.php index 76e68d8ab..3d83c9450 100644 --- a/src/Navigation/Item.php +++ b/src/Navigation/Item.php @@ -6,6 +6,12 @@ use Cone\Root\Traits\Makeable; use Illuminate\Support\Facades\URL; +/** + * @property string $url + * @property string $label + * @property string $icon + * @property string $group + */ class Item { use HasAttributes; diff --git a/src/Support/Slug.php b/src/Support/Slug.php new file mode 100644 index 000000000..7e9822ba3 --- /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(); + } +} diff --git a/src/Widgets/Widgets.php b/src/Widgets/Widgets.php index bff732c21..6bf2a6f49 100644 --- a/src/Widgets/Widgets.php +++ b/src/Widgets/Widgets.php @@ -6,6 +6,9 @@ use Illuminate\Support\Collection; use Illuminate\Support\Traits\ForwardsCalls; +/** + * @mixin \Illuminate\Support\Collection + */ class Widgets { use ForwardsCalls; From 2ececb03f3f2634d9415f7b5beeeca856489e155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 02:55:48 +0000 Subject: [PATCH 2/7] fix --- src/Support/Slug.php | 170 ------------------------------------------- 1 file changed, 170 deletions(-) delete mode 100644 src/Support/Slug.php diff --git a/src/Support/Slug.php b/src/Support/Slug.php deleted file mode 100644 index 7e9822ba3..000000000 --- a/src/Support/Slug.php +++ /dev/null @@ -1,170 +0,0 @@ -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(); - } -} From f3886e108b61ff92384327f0d7b251d5a07204af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 03:07:09 +0000 Subject: [PATCH 3/7] fix --- src/Fields/Fieldset.php | 2 +- src/Fields/File.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Fields/Fieldset.php b/src/Fields/Fieldset.php index 59b105f59..141d9cf39 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/File.php b/src/Fields/File.php index c62fa5a86..7f57aa9ff 100644 --- a/src/Fields/File.php +++ b/src/Fields/File.php @@ -222,6 +222,8 @@ protected function prune(Request $request, Model $model, array $keys): int */ public function toOption(Request $request, Model $model, Model $related): array { + /** @var \Cone\Root\Fields\File $related */ + $option = parent::toOption($request, $model, $related); $name = sprintf( From ef11498d4133a65b373508fd10ad12118f54612f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 03:13:24 +0000 Subject: [PATCH 4/7] fix --- src/Fields/File.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Fields/File.php b/src/Fields/File.php index 7f57aa9ff..b3c48b7eb 100644 --- a/src/Fields/File.php +++ b/src/Fields/File.php @@ -222,8 +222,6 @@ protected function prune(Request $request, Model $model, array $keys): int */ public function toOption(Request $request, Model $model, Model $related): array { - /** @var \Cone\Root\Fields\File $related */ - $option = parent::toOption($request, $model, $related); $name = sprintf( @@ -235,6 +233,8 @@ public function toOption(Request $request, Model $model, Model $related): array $option['attrs']->merge(['name' => $name]); + /** @var \Cone\Root\Fields\File $related */ + return array_merge($option, [ 'file_name' => $related->file_name, 'is_image' => $related->isImage, From 44f79d4efdb53c24b5460cc3987747136c71526f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 03:15:52 +0000 Subject: [PATCH 5/7] fix --- src/Fields/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Fields/File.php b/src/Fields/File.php index b3c48b7eb..187375b2b 100644 --- a/src/Fields/File.php +++ b/src/Fields/File.php @@ -233,7 +233,7 @@ public function toOption(Request $request, Model $model, Model $related): array $option['attrs']->merge(['name' => $name]); - /** @var \Cone\Root\Fields\File $related */ + /** @var \Cone\Root\Models\Medium $related */ return array_merge($option, [ 'file_name' => $related->file_name, From 355215fd42b8991330b860c36923aca2e8f5fdc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 03:24:11 +0000 Subject: [PATCH 6/7] fix --- src/Fields/Slug.php | 3 ++- src/Http/Controllers/ActionController.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Fields/Slug.php b/src/Fields/Slug.php index 49966bf10..44fb50763 100644 --- a/src/Fields/Slug.php +++ b/src/Fields/Slug.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Http\Request; use Illuminate\Support\Str; use Illuminate\Validation\Rule; @@ -152,7 +153,7 @@ static function (Builder $query): Builder { $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], ((int) $match[1]) + 1, $match[0]); + return str_replace($match[1], (string) (((int) $match[1]) + 1), $match[0]); }, $match ); diff --git a/src/Http/Controllers/ActionController.php b/src/Http/Controllers/ActionController.php index 6d0c4c6df..4f0421e1a 100644 --- a/src/Http/Controllers/ActionController.php +++ b/src/Http/Controllers/ActionController.php @@ -13,6 +13,7 @@ class ActionController extends Controller */ public function __invoke(Request $request): RedirectResponse { + /** @var \Cone\Root\Actions\Action $action */ $action = $request->route('rootAction'); Gate::allowIf($action->authorized($request)); From a7703fbf8ca39a4a563c1ed1b6650ef62700ebe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Viktor=20Sz=C3=A9pe?= Date: Sat, 21 Oct 2023 02:47:58 +0000 Subject: [PATCH 7/7] Prepare PHPStan --- phpstan.neon.dist | 3 --- 1 file changed, 3 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9312cf752..eee11b917 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -25,9 +25,6 @@ parameters: ignoreErrors: - '#^Unsafe usage of new static#' # --- TODO-s --- - # Tricky readonlys - - '#Assign it in the constructor\.$#' - - '#is assigned outside of the constructor\.$#' # X vs. X contract - '#but returns Illuminate\\Contracts\\#' # SoftDeletes