From 0c9c6009a64488bb4c81289c3f5113b77d99e175 Mon Sep 17 00:00:00 2001 From: Alexander Wenzel Date: Tue, 5 Jul 2022 01:51:12 +0200 Subject: [PATCH] fixed form validation for nested DependencyContainer fields (#4) nested DependencyContainer fields are now correctly validated --- readme.md | 58 ++++++++------------ src/DependencyContainer.php | 104 +++++++++++++++++++++++------------- 2 files changed, 90 insertions(+), 72 deletions(-) diff --git a/readme.md b/readme.md index 83f6484..c63878f 100644 --- a/readme.md +++ b/readme.md @@ -1,22 +1,16 @@ # nova 4 dependency container -This plugin based on [epartment/nova-dependency-container](https://github.com/epartment/nova-dependency-container) and -only supports Nova 4 and php 8. - -### Description - A Laravel Nova 4 form container for grouping fields that depend on other field values. Dependencies can be set on any field type or value. -### Demo - -![Demo](https://raw.githubusercontent.com/alexwenzel/nova-dependency-container/master/docs/demo.gif) +This plugin is based on [epartment/nova-dependency-container](https://github.com/epartment/nova-dependency-container) +and only supports **Nova 4.x** and **PHP 8.x**. -### Versions +## Demo -This Nova plugin only works with **Nova 4.x** and **PHP 8.x** +![Demo](https://raw.githubusercontent.com/alexwenzel/nova-dependency-container/master/docs/demo.gif) -### Installation +## Installation The package can be installed through Composer. @@ -24,10 +18,10 @@ The package can be installed through Composer. composer require alexwenzel/nova-dependency-container ``` -### Usage +## Usage 1. Add the `Alexwenzel\DependencyContainer\HasDependencies` trait to your Nova Resource. -2. Add the `Alexwenzel\DependencyContainer\DependencyContainer` to your Nova Resource `fields` method. +2. Add the `Alexwenzel\DependencyContainer\DependencyContainer` to your Nova Resource `fields()` method. 3. Add the `Alexwenzel\DependencyContainer\ActionHasDependencies` trait to your Nova Actions that you wish to use dependencies on. @@ -39,7 +33,6 @@ class Page extends Resource public function fields(Request $request) { return [ - Select::make('Name format', 'name_format')->options([ 0 => 'First Name', 1 => 'First Name / Last Name', @@ -49,15 +42,14 @@ class Page extends Resource DependencyContainer::make([ Text::make('First Name', 'first_name') ])->dependsOn('name_format', 0), - ]; } } ``` -### Dependencies +## Available dependencies -The package supports this kinds of dependencies: +The package supports these kinds of dependencies: 1. `->dependsOn('field', 'value')` 2. `->dependsOnNot('field', 'value')` @@ -67,7 +59,7 @@ The package supports this kinds of dependencies: 6. `->dependsOnIn('field', [array])` 7. `->dependsOnNotIn('field', [array])` -These dependencies can be combined by chaining the methods on the `DependencyContainer`: +These dependencies can be combined by chaining the methods on the `DependencyContainer` field: ```php DependencyContainer::make([ @@ -85,7 +77,7 @@ Here is an example using a checkbox: ![Demo](https://raw.githubusercontent.com/alexwenzel/nova-dependency-container/master/docs/demo-2.gif) -### BelongsTo dependency +## BelongsTo dependency If we follow the example of a *Post model belongsTo a User model*, taken from Novas documentation [BelongsTo](https://nova.laravel.com/docs/2.0/resources/relationships.html#belongsto), the dependency @@ -106,7 +98,7 @@ DependencyContainer::make([ When the `Post` resource with `id` 2 is being selected, a `Boolean` field will appear. -### BelongsToMany dependency +## BelongsToMany dependency A [BelongsToMany](https://nova.laravel.com/docs/2.0/resources/relationships.html#belongstomany) setup is similar to that of a [BelongsTo](https://nova.laravel.com/docs/2.0/resources/relationships.html#belongsto). @@ -141,20 +133,16 @@ Here is an (ugly) example of a get/set mutator setup for an intermediate table u ```php // model User class User ... { - - public function roles() { - return $this->belongsToMany->using(RoleUser::class)->withPivot('rules_all'); - } - + public function roles() { + return $this->belongsToMany->using(RoleUser::class)->withPivot('rules_all'); + } } // model Role class Role ... { - - public function users() { - return $this->belongsToMany->using(RoleUser::class)->withPivot('rules_all'); - } - + public function users() { + return $this->belongsToMany->using(RoleUser::class)->withPivot('rules_all'); + } } // intermediate table @@ -188,8 +176,7 @@ And now for the dependency container. ]) ->displayUsingLabels() ]) - ->dependsOn('role_user', 1) - , + ->dependsOn('role_user', 1), DependencyContainer::make([ // pivot field rules_all @@ -199,15 +186,14 @@ And now for the dependency container. ]) ->displayUsingLabels() ]) - ->dependsOn('role_user', 2) - , + ->dependsOn('role_user', 2), // .. and so on ] }), ``` -### MorphTo dependency +## MorphTo dependency A similar example taken from Novas documentation for [MorphTo](https://nova.laravel.com/docs/2.0/resources/relationships.html#morphto) is called commentable. It uses 3 @@ -232,7 +218,7 @@ DependencyContainer::make([ ->dependsOn('commentable', 'Post') ``` -### License +## License The MIT License (MIT). Please see [License File](https://github.com/alexwenzel/nova-dependency-container/blob/master/LICENSE.md) for more information. diff --git a/src/DependencyContainer.php b/src/DependencyContainer.php index 2c155b4..350d633 100644 --- a/src/DependencyContainer.php +++ b/src/DependencyContainer.php @@ -2,6 +2,7 @@ namespace Alexwenzel\DependencyContainer; +use Illuminate\Support\Arr; use Laravel\Nova\Fields\Field; use Laravel\Nova\Http\Requests\NovaRequest; use Illuminate\Support\Str; @@ -24,8 +25,8 @@ class DependencyContainer extends Field * DependencyContainer constructor. * * @param $fields - * @param null $attribute - * @param null $resolveCallback + * @param null $attribute + * @param null $resolveCallback */ public function __construct($fields, $attribute = null, $resolveCallback = null) { @@ -46,8 +47,8 @@ public function dependsOn($field, $value) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - $this->getFieldLayout($field, $value) - ]) + $this->getFieldLayout($field, $value), + ]), ]); } @@ -61,8 +62,8 @@ public function dependsOnNot($field, $value) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['not' => $value]) - ]) + array_merge($this->getFieldLayout($field), ['not' => $value]), + ]), ]); } @@ -76,8 +77,8 @@ public function dependsOnEmpty($field) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['empty' => true]) - ]) + array_merge($this->getFieldLayout($field), ['empty' => true]), + ]), ]); } @@ -91,8 +92,8 @@ public function dependsOnNotEmpty($field) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['notEmpty' => true]) - ]) + array_merge($this->getFieldLayout($field), ['notEmpty' => true]), + ]), ]); } @@ -107,8 +108,8 @@ public function dependsOnNullOrZero($field) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['nullOrZero' => true]) - ]) + array_merge($this->getFieldLayout($field), ['nullOrZero' => true]), + ]), ]); } @@ -123,8 +124,8 @@ public function dependsOnIn($field, $array) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['in' => $array]) - ]) + array_merge($this->getFieldLayout($field), ['in' => $array]), + ]), ]); } @@ -139,8 +140,8 @@ public function dependsOnNotIn($field, $array) { return $this->withMeta([ 'dependencies' => array_merge($this->meta['dependencies'], [ - array_merge($this->getFieldLayout($field), ['notin' => $array]) - ]) + array_merge($this->getFieldLayout($field), ['notin' => $array]), + ]), ]); } @@ -160,19 +161,19 @@ protected function getFieldLayout($field, $value = null) } return [ // literal form input name - 'field' => $field[0], + 'field' => $field[0], // property to compare 'property' => $field[1], // value to compare - 'value' => $value, + 'value' => $value, ]; } /** * Resolve dependency fields for display * - * @param mixed $resource - * @param null $attribute + * @param mixed $resource + * @param null $attribute */ public function resolveForDisplay($resource, $attribute = null) { @@ -226,8 +227,8 @@ public function resolveForDisplay($resource, $attribute = null) continue; } // @todo: quickfix for MorphTo - $morphable_attribute = $resource->getAttribute($dependency['property'].'_type'); - if ($morphable_attribute !== null && Str::endsWith($morphable_attribute, '\\'.$dependency['value'])) { + $morphable_attribute = $resource->getAttribute($dependency['property'] . '_type'); + if ($morphable_attribute !== null && Str::endsWith($morphable_attribute, '\\' . $dependency['value'])) { $this->meta['dependencies'][$index]['satisfied'] = true; continue; } @@ -239,8 +240,8 @@ public function resolveForDisplay($resource, $attribute = null) /** * Resolve dependency fields * - * @param mixed $resource - * @param string $attribute + * @param mixed $resource + * @param string $attribute * @return array|mixed */ public function resolve($resource, $attribute = null) @@ -255,10 +256,10 @@ public function resolve($resource, $attribute = null) * * @trace fill/fillForAction -> fillInto -> * * - * @param NovaRequest $request + * @param NovaRequest $request * @param $model * @param $attribute - * @param null $requestAttribute + * @param null $requestAttribute */ public function fillInto(NovaRequest $request, $model, $attribute, $requestAttribute = null) { @@ -270,7 +271,7 @@ public function fillInto(NovaRequest $request, $model, $attribute, $requestAttri /** * Checks whether to add validation rules * - * @param NovaRequest $request + * @param NovaRequest $request * @return bool */ public function areDependenciesSatisfied(NovaRequest $request) @@ -313,13 +314,17 @@ public function areDependenciesSatisfied(NovaRequest $request) /** * Get a rule set based on field property name * - * @param NovaRequest $request - * @param string $propertyName + * @param NovaRequest $request + * @param string $propertyName * @return array */ protected function getSituationalRulesSet(NovaRequest $request, string $propertyName = 'rules') { $fieldsRules = [$this->attribute => []]; + + // if dependencies are not satisfied + // or no fields as dependency exist + // return empty rules for dependency container if (!$this->areDependenciesSatisfied($request) || !isset($this->meta['fields']) || !is_array($this->meta['fields'])) { @@ -328,18 +333,45 @@ protected function getSituationalRulesSet(NovaRequest $request, string $property /** @var Field $field */ foreach ($this->meta['fields'] as $field) { - $fieldsRules[$field->attribute] = is_callable($field->{$propertyName}) - ? call_user_func($field->{$propertyName}, $request) - : $field->{$propertyName}; + // if field is DependencyContainer, then add rules from dependant fields + if ($field instanceof DependencyContainer && $propertyName === "rules") { + $fieldsRules[Str::random()] = $field->getRules($request); + } else { + $fieldsRules[$field->attribute] = is_callable($field->{$propertyName}) + ? call_user_func($field->{$propertyName}, $request) + : $field->{$propertyName}; + } + } + + // simplify nested rules to one level + return $this->array_simplify($fieldsRules); + } + + /** + * @param $array + * @return array + */ + protected function array_simplify($array): array + { + $result = []; + + foreach ($array as $key => $value) { + if (is_string($key) && is_array($value) && !empty($value)) { + if (count(array_filter(array_keys($value), 'is_string')) > 0) { + $result = array_merge($result, $this->array_simplify($value)); + } else { + $result[$key] = $value; + } + } } - return $fieldsRules; + return $result; } /** * Get the validation rules for this field. * - * @param NovaRequest $request + * @param NovaRequest $request * @return array */ public function getRules(NovaRequest $request) @@ -350,7 +382,7 @@ public function getRules(NovaRequest $request) /** * Get the creation rules for this field. * - * @param NovaRequest $request + * @param NovaRequest $request * @return array|string */ public function getCreationRules(NovaRequest $request) @@ -366,7 +398,7 @@ public function getCreationRules(NovaRequest $request) /** * Get the update rules for this field. * - * @param NovaRequest $request + * @param NovaRequest $request * @return array */ public function getUpdateRules(NovaRequest $request)