diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index 8b360762127e..9c3d6adbec99 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -259,8 +259,8 @@ protected function componentString(string $component, array $attributes) } return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).']) -getConstructor()): ?> -except(collect($constructor->getParameters())->map->getName()->all()); ?> + +except(\\'.$class.'::ignoredParameterNames()); ?> withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; } diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php index 88617186fb20..56d1399e85dc 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php @@ -68,7 +68,7 @@ public static function compileClassComponentOpening(string $component, string $a return implode("\n", [ '', '', - 'getIterator() : [])); ?>', + 'all() : [])); ?>', 'withName('.$alias.'); ?>', 'shouldRender()): ?>', 'startComponent($component->resolveView(), $component->data()); ?>', @@ -157,19 +157,35 @@ protected function compileEndComponentFirst() */ protected function compileProps($expression) { - return " -onlyProps{$expression} as \$__key => \$__value) { - \$\$__key = \$\$__key ?? \$__value; -} ?> -exceptProps{$expression}; ?> - \$__value) { + return "all() as \$__key => \$__value) { + if (in_array(\$__key, \$__propNames)) { + \$\$__key = \$\$__key ?? \$__value; + } else { + \$__newAttributes[\$__key] = \$__value; + } +} + +\$attributes = new \Illuminate\View\ComponentAttributeBag(\$__newAttributes); + +unset(\$__propNames); +unset(\$__newAttributes); + +foreach (array_filter({$expression}, 'is_string', ARRAY_FILTER_USE_KEY) as \$__key => \$__value) { \$\$__key = \$\$__key ?? \$__value; -} ?> - - \$__value) { +} + +\$__defined_vars = get_defined_vars(); + +foreach (\$attributes->all() as \$__key => \$__value) { if (array_key_exists(\$__key, \$__defined_vars)) unset(\$\$__key); -} ?> -"; +} + +unset(\$__defined_vars); ?>"; } /** diff --git a/src/Illuminate/View/Component.php b/src/Illuminate/View/Component.php index 6768fdcbb1c2..fa21f08ab5ff 100644 --- a/src/Illuminate/View/Component.php +++ b/src/Illuminate/View/Component.php @@ -75,6 +75,13 @@ abstract class Component */ protected static $constructorParametersCache = []; + /** + * The cache of ignored parameter names. + * + * @var array + */ + protected static $ignoredParameterNames = []; + /** * Get the view / view contents that represent the component. * @@ -417,6 +424,30 @@ protected function factory() return static::$factory; } + /** + * Get the cached set of anonymous component constructor parameter names to exclude. + * + * @return array + */ + public static function ignoredParameterNames() + { + if (! isset(static::$ignoredParameterNames[static::class])) { + $constructor = (new ReflectionClass( + static::class + ))->getConstructor(); + + if (! $constructor) { + return static::$ignoredParameterNames[static::class] = []; + } + + static::$ignoredParameterNames[static::class] = collect($constructor->getParameters()) + ->map->getName() + ->all(); + } + + return static::$ignoredParameterNames[static::class]; + } + /** * Flush the component's cached state. * diff --git a/src/Illuminate/View/ComponentAttributeBag.php b/src/Illuminate/View/ComponentAttributeBag.php index 368391c038ff..868a97f5aac3 100644 --- a/src/Illuminate/View/ComponentAttributeBag.php +++ b/src/Illuminate/View/ComponentAttributeBag.php @@ -37,6 +37,16 @@ public function __construct(array $attributes = []) $this->attributes = $attributes; } + /** + * Get all of the attribute values. + * + * @return array + */ + public function all() + { + return $this->attributes; + } + /** * Get the first attribute's value. * @@ -207,7 +217,7 @@ public function thatStartWith($needles) */ public function onlyProps($keys) { - return $this->only($this->extractPropNames($keys)); + return $this->only(static::extractPropNames($keys)); } /** @@ -218,27 +228,7 @@ public function onlyProps($keys) */ public function exceptProps($keys) { - return $this->except($this->extractPropNames($keys)); - } - - /** - * Extract prop names from given keys. - * - * @param mixed|array $keys - * @return array - */ - protected function extractPropNames($keys) - { - $props = []; - - foreach ($keys as $key => $defaultValue) { - $key = is_numeric($key) ? $defaultValue : $key; - - $props[] = $key; - $props[] = Str::kebab($key); - } - - return $props; + return $this->except(static::extractPropNames($keys)); } /** @@ -401,6 +391,26 @@ public function setAttributes(array $attributes) $this->attributes = $attributes; } + /** + * Extract "prop" names from given keys. + * + * @param array $keys + * @return array + */ + public static function extractPropNames(array $keys) + { + $props = []; + + foreach ($keys as $key => $default) { + $key = is_numeric($key) ? $default : $key; + + $props[] = $key; + $props[] = Str::kebab($key); + } + + return $props; + } + /** * Get content as a string of HTML. * diff --git a/src/Illuminate/View/Concerns/ManagesEvents.php b/src/Illuminate/View/Concerns/ManagesEvents.php index d6cd9d81b113..7ad3c689d00e 100644 --- a/src/Illuminate/View/Concerns/ManagesEvents.php +++ b/src/Illuminate/View/Concerns/ManagesEvents.php @@ -174,7 +174,9 @@ protected function addEventListener($name, $callback) */ public function callComposer(ViewContract $view) { - $this->events->dispatch('composing: '.$view->name(), [$view]); + if ($this->events->hasListeners($event = 'composing: '.$view->name())) { + $this->events->dispatch($event, [$view]); + } } /** @@ -185,6 +187,8 @@ public function callComposer(ViewContract $view) */ public function callCreator(ViewContract $view) { - $this->events->dispatch('creating: '.$view->name(), [$view]); + if ($this->events->hasListeners($event = 'creating: '.$view->name())) { + $this->events->dispatch($event, [$view]); + } } } diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 5143ae503c72..38a4a037b21a 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -103,13 +103,13 @@ public function testBasicComponentParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". "@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -121,8 +121,8 @@ public function testBasicComponentWithEmptyAttributesParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['type' => '','limit' => '','@click' => '','required' => true]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -134,8 +134,8 @@ public function testDataCamelCasing() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -146,8 +146,8 @@ public function testColonData() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -158,8 +158,8 @@ public function testColonDataShortSyntax() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -170,8 +170,8 @@ public function testColonDataWithStaticClassProperty() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -182,16 +182,16 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -202,8 +202,8 @@ public function testSelfClosingComponentWithColonDataShortSyntax() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -215,8 +215,8 @@ public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShort $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -228,8 +228,8 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -237,8 +237,8 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestInputComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -250,8 +250,8 @@ public function testEscapedColonAttribute() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([':title' => 'user.name']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -262,8 +262,8 @@ public function testColonAttributesIsEscapedIfStrings() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -274,8 +274,8 @@ public function testClassDirective() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -286,8 +286,8 @@ public function testStyleDirective() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -298,8 +298,8 @@ public function testColonNestedComponentParsing() $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -310,8 +310,8 @@ public function testColonStartingNestedComponentParsing() $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -322,8 +322,8 @@ public function testSelfClosingComponentsCanBeCompiled() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -364,8 +364,8 @@ public function testComponentsCanBeCompiledWithHyphenAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','wire:model' => 'foo','x-on:click' => 'bar','@click' => 'baz']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -377,8 +377,8 @@ public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -391,8 +391,8 @@ public function testComponentCanReceiveAttributeBag() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes),'wire:model' => 'foo']); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } @@ -404,8 +404,8 @@ public function testSelfClosingComponentCanReceiveAttributeBag() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar','attributes' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$attributes->merge(['class' => 'test'])),'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##
', trim($result)); @@ -417,8 +417,8 @@ public function testComponentsCanHaveAttachedWord() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestProfileComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##Words", trim($result)); } @@ -429,8 +429,8 @@ public function testSelfClosingComponentsCanHaveAttachedWord() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##Words', trim($result)); @@ -442,8 +442,8 @@ public function testSelfClosingComponentsCanBeCompiledWithBoundData() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes(['class' => 'bar']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -456,8 +456,8 @@ public function testPairedComponentTags() '); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\Tests\View\Blade\TestAlertComponent::ignoredParameterNames()); ?> withAttributes([]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); @@ -475,8 +475,8 @@ public function testClasslessComponents() $result = $this->compiler()->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -494,8 +494,8 @@ public function testClasslessComponentsWithIndexView() $result = $this->compiler()->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -513,8 +513,8 @@ public function testPackagesClasslessComponents() $result = $this->compiler()->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -547,8 +547,8 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() $result = $compiler->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -581,8 +581,8 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV $result = $compiler->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -614,8 +614,8 @@ public function testClasslessComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); @@ -647,8 +647,8 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) -getConstructor()): ?> -except(collect(\$constructor->getParameters())->map->getName()->all()); ?> + +except(\Illuminate\View\AnonymousComponent::ignoredParameterNames()); ?> withAttributes([]); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index 2615edf91452..64231b4fd098 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -18,7 +18,7 @@ public function testClassComponentsAreCompiled() { $this->assertSame(' - "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? (array) $attributes->getIterator() : [])); ?> + "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? $attributes->all() : [])); ?> withName(\'test\'); ?> shouldRender()): ?> startComponent($component->resolveView(), $component->data()); ?>', $this->compiler->compileString('@component(\'Illuminate\Tests\View\Blade\ComponentStub::class\', \'test\', ["foo" => "bar"])')); diff --git a/tests/View/Blade/BladePropsTest.php b/tests/View/Blade/BladePropsTest.php index a1dfa1cc6653..e9d258e2265f 100644 --- a/tests/View/Blade/BladePropsTest.php +++ b/tests/View/Blade/BladePropsTest.php @@ -8,19 +8,35 @@ class BladePropsTest extends AbstractBladeTestCase { public function testPropsAreCompiled() { - $this->assertSame(' -onlyProps([\'one\' => true, \'two\' => \'string\']) as $__key => $__value) { - $$__key = $$__key ?? $__value; -} ?> -exceptProps([\'one\' => true, \'two\' => \'string\']); ?> - true, \'two\' => \'string\']), \'is_string\', ARRAY_FILTER_USE_KEY) as $__key => $__value) { + $this->assertSame(' true, \'two\' => \'string\'])); + +foreach ($attributes->all() as $__key => $__value) { + if (in_array($__key, $__propNames)) { + $$__key = $$__key ?? $__value; + } else { + $__newAttributes[$__key] = $__value; + } +} + +$attributes = new \Illuminate\View\ComponentAttributeBag($__newAttributes); + +unset($__propNames); +unset($__newAttributes); + +foreach (array_filter(([\'one\' => true, \'two\' => \'string\']), \'is_string\', ARRAY_FILTER_USE_KEY) as $__key => $__value) { $$__key = $$__key ?? $__value; -} ?> - - $__value) { +} + +$__defined_vars = get_defined_vars(); + +foreach ($attributes->all() as $__key => $__value) { if (array_key_exists($__key, $__defined_vars)) unset($$__key); -} ?> -', $this->compiler->compileString('@props([\'one\' => true, \'two\' => \'string\'])')); +} + +unset($__defined_vars); ?>', $this->compiler->compileString('@props([\'one\' => true, \'two\' => \'string\'])')); } public function testPropsAreExtractedFromParentAttributesCorrectly() diff --git a/tests/View/ViewFactoryTest.php b/tests/View/ViewFactoryTest.php index 85ea0039dfc6..5465c78af0b2 100755 --- a/tests/View/ViewFactoryTest.php +++ b/tests/View/ViewFactoryTest.php @@ -148,7 +148,7 @@ public function testEnvironmentAddsExtensionWithCustomResolver() $factory->getEngineResolver()->shouldReceive('register')->once()->with('bar', $resolver); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn('path.foo'); $factory->getEngineResolver()->shouldReceive('resolve')->once()->with('bar')->andReturn($engine = m::mock(Engine::class)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->addExtension('foo', 'bar', $resolver); @@ -185,11 +185,14 @@ public function testPrependedExtensionOverridesExistingExtensions() public function testCallCreatorsDoesDispatchEventsWhenIsNecessary() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('creating: name', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('creating: name', m::type('array')) @@ -206,11 +209,14 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessary() public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespacedWildcards() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('creating: namespaced::*', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('creating: namespaced::my-package-view', m::type('array')) @@ -227,6 +233,7 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespaced public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespacedNestedWildcards() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('creating: namespaced::*', m::type(Closure::class)) @@ -237,6 +244,8 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespaced ->with('creating: welcome', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('creating: namespaced::my-package-view', m::type('array')) @@ -253,11 +262,14 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNamespaced public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingWildcards() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('creating: *', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('creating: name', m::type('array')) @@ -274,11 +286,14 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingWildcards( public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNormalizedNames() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('creating: components.button', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('creating: components/button', m::type('array')) @@ -297,11 +312,14 @@ public function testCallCreatorsDoesDispatchEventsWhenIsNecessaryUsingNormalized public function testCallComposerDoesDispatchEventsWhenIsNecessary() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('composing: name', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('composing: name', m::type('array')) @@ -318,11 +336,14 @@ public function testCallComposerDoesDispatchEventsWhenIsNecessary() public function testCallComposerDoesDispatchEventsWhenIsNecessaryAndUsingTheArrayFormat() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('composing: name', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('composing: name', m::type('array')) @@ -339,11 +360,14 @@ public function testCallComposerDoesDispatchEventsWhenIsNecessaryAndUsingTheArra public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNamespacedWildcards() { $factory = $this->getFactory(); + $factory->getDispatcher() ->shouldReceive('listen') ->with('composing: namespaced::*', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('composing: namespaced::my-package-view', m::type('array')) @@ -370,6 +394,8 @@ public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNamespace ->with('composing: welcome', m::type(Closure::class)) ->once(); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('dispatch') ->with('composing: namespaced::my-package-view', m::type('array')) @@ -386,6 +412,9 @@ public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNamespace public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingWildcards() { $factory = $this->getFactory(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('listen') ->with('composing: *', m::type(Closure::class)) @@ -407,6 +436,9 @@ public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingWildcards public function testCallComposersDoesDispatchEventsWhenIsNecessaryUsingNormalizedNames() { $factory = $this->getFactory(); + + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); + $factory->getDispatcher() ->shouldReceive('listen') ->with('composing: components.button', m::type(Closure::class)) @@ -496,6 +528,7 @@ public function testCallComposerCallsProperEvent() $factory->composer('name', fn () => true); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(true); $factory->getDispatcher()->shouldReceive('dispatch')->once()->with('composing: name', [$view]); $factory->callComposer($view); @@ -612,7 +645,7 @@ public function testComponentHandling() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent('component', ['name' => 'Taylor']); $factory->slot('title'); $factory->slot('website', 'laravel.com', []); @@ -628,7 +661,7 @@ public function testComponentHandlingUsingViewObject() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent($factory->make('component'), ['name' => 'Taylor']); $factory->slot('title'); $factory->slot('website', 'laravel.com', []); @@ -644,7 +677,7 @@ public function testComponentHandlingUsingClosure() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->andReturn(__DIR__.'/fixtures/component.php'); $factory->getEngineResolver()->shouldReceive('resolve')->andReturn(new PhpEngine(new Filesystem)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->startComponent(function ($data) use ($factory) { $this->assertArrayHasKey('name', $data); $this->assertSame($data['name'], 'Taylor'); @@ -810,7 +843,7 @@ public function testMakeWithSlashAndDot() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->twice()->with('foo.bar')->andReturn('path.php'); $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->make('foo/bar'); $factory->make('foo.bar'); } @@ -820,7 +853,7 @@ public function testNamespacedViewNamesAreNormalizedProperly() $factory = $this->getFactory(); $factory->getFinder()->shouldReceive('find')->twice()->with('vendor/package::foo.bar')->andReturn('path.php'); $factory->getEngineResolver()->shouldReceive('resolve')->twice()->with('php')->andReturn(m::mock(Engine::class)); - $factory->getDispatcher()->shouldReceive('dispatch'); + $factory->getDispatcher()->shouldReceive('hasListeners')->andReturn(false); $factory->make('vendor/package::foo/bar'); $factory->make('vendor/package::foo.bar'); } @@ -848,7 +881,7 @@ public function testExceptionsInSectionsAreThrown() $factory->getEngineResolver()->shouldReceive('resolve')->twice()->andReturn($engine); $factory->getFinder()->shouldReceive('find')->once()->with('layout')->andReturn(__DIR__.'/fixtures/section-exception-layout.php'); $factory->getFinder()->shouldReceive('find')->once()->with('view')->andReturn(__DIR__.'/fixtures/section-exception.php'); - $factory->getDispatcher()->shouldReceive('dispatch')->times(4); // 2 "creating" + 2 "composing"... + $factory->getDispatcher()->shouldReceive('hasListeners')->times(4); // 2 "creating" + 2 "composing"... $factory->make('view')->render(); }