From 0dbb8033e41067bf3cb921a111215b78f957a7eb Mon Sep 17 00:00:00 2001 From: Rudie Dirkx Date: Mon, 28 Oct 2024 02:24:34 +0100 Subject: [PATCH] +phpstan generics --- phpstan-extension.neon | 5 ++ .../Fields/ChildFormType.php | 8 ++ .../LaravelFormBuilder/Fields/ChoiceType.php | 5 +- .../Fields/CollectionType.php | 7 ++ .../LaravelFormBuilder/Fields/ParentType.php | 7 ++ .../Fields/RepeatedType.php | 3 + .../PhpStan/FormGetFieldExtension.php | 73 +++++++++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 phpstan-extension.neon create mode 100644 src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php diff --git a/phpstan-extension.neon b/phpstan-extension.neon new file mode 100644 index 00000000..9f989f83 --- /dev/null +++ b/phpstan-extension.neon @@ -0,0 +1,5 @@ +services: + - + class: Kris\LaravelFormBuilder\PhpStan\FormGetFieldExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension diff --git a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php index 5c35689a..1be9df76 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChildFormType.php @@ -4,11 +4,17 @@ use Kris\LaravelFormBuilder\Form; +/** + * @template TFormType of Form + * + * @extends ParentType + */ class ChildFormType extends ParentType { /** * @var Form + * @phpstan-var TFormType */ protected $form; @@ -22,6 +28,7 @@ protected function getTemplate() /** * @return Form + * @phpstan-return TFormType */ public function getForm() { @@ -96,6 +103,7 @@ protected function createChildren() /** * @return Form + * @phpstan-return TFormType * @throws \Exception */ protected function getClassFromOptions() diff --git a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php index 026d4a46..85ee0fcf 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ChoiceType.php @@ -4,6 +4,9 @@ use Illuminate\Support\Arr; +/** + * @extends ParentType + */ class ChoiceType extends ParentType { /** @@ -76,7 +79,7 @@ protected function createChildren() if (($data_override = $this->getOption('data_override')) && $data_override instanceof \Closure) { $this->options['choices'] = $data_override($this->options['choices'], $this); } - + $this->children = []; $this->determineChoiceField(); diff --git a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php index f51b679f..a06d2989 100644 --- a/src/Kris/LaravelFormBuilder/Fields/CollectionType.php +++ b/src/Kris/LaravelFormBuilder/Fields/CollectionType.php @@ -5,12 +5,18 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +/** + * @template TType of FormField + * + * @extends ParentType + */ class CollectionType extends ParentType { /** * Contains template for a collection element. * * @var FormField + * @phpstan-var TType */ protected $proto; @@ -49,6 +55,7 @@ protected function getDefaults() * Get the prototype object. * * @return FormField + * @phpstan-return TType * @throws \Exception */ public function prototype() diff --git a/src/Kris/LaravelFormBuilder/Fields/ParentType.php b/src/Kris/LaravelFormBuilder/Fields/ParentType.php index 80352d0c..1b92d18d 100644 --- a/src/Kris/LaravelFormBuilder/Fields/ParentType.php +++ b/src/Kris/LaravelFormBuilder/Fields/ParentType.php @@ -5,11 +5,15 @@ use Illuminate\Support\Arr; use Kris\LaravelFormBuilder\Form; +/** + * @template TChildType of FormField + */ abstract class ParentType extends FormField { /** * @var FormField[] + * @phpstan-var TChildType[] */ protected $children; @@ -64,6 +68,7 @@ public function render(array $options = [], $showLabel = true, $showField = true * Get all children of the choice field. * * @return mixed + * @phpstan-return TChildType[] */ public function getChildren() { @@ -74,6 +79,7 @@ public function getChildren() * Get a child of the choice field. * * @return mixed + * @phpstan-return ?TChildType */ public function getChild($key) { @@ -145,6 +151,7 @@ public function isRendered() * * @param string $name * @return FormField + * @phpstan-return TChildType */ public function __get($name) { diff --git a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php index 0afaff78..2c46055f 100644 --- a/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php +++ b/src/Kris/LaravelFormBuilder/Fields/RepeatedType.php @@ -4,6 +4,9 @@ use Illuminate\Support\Arr; +/** + * @extends ParentType + */ class RepeatedType extends ParentType { diff --git a/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php b/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php new file mode 100644 index 00000000..f03bb4ce --- /dev/null +++ b/src/Kris/LaravelFormBuilder/PhpStan/FormGetFieldExtension.php @@ -0,0 +1,73 @@ +getName() == 'getField'; + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { + return $this->getTypeFromMethodCallGetField($methodReflection, $methodCall, $scope); + } + + protected function getTypeFromMethodCallGetField(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + if (count($methodCall->getArgs()) < 1) { + return null; + } + + $fieldNameArg = $methodCall->getArgs()[0]->value; + if (!($fieldNameArg instanceof String_)) { + return null; + } + + $fieldName = $fieldNameArg->value; + + $calledOnType = $scope->getType($methodCall->var); + assert($calledOnType instanceof TypeWithClassName); + $formClass = $calledOnType->getClassName(); + + $formClassReflection = $this->reflectionProvider->getClass($formClass); + + if (!$formClassReflection->hasProperty($fieldName)) { + return null; + } + + $formClassFieldProperty = $formClassReflection->getProperty($fieldName, $scope); + if (!($formClassFieldProperty instanceof AnnotationPropertyReflection)) { + return null; + } + + return $formClassFieldProperty->getReadableType(); + } + +}