Skip to content

Commit

Permalink
Merge branch 'symfony:2.x' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
yassinefikri authored Aug 8, 2023
2 parents e890f93 + 02ae73b commit 8dad37e
Show file tree
Hide file tree
Showing 32 changed files with 535 additions and 15 deletions.
4 changes: 4 additions & 0 deletions src/TwigComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.11.0

- Support ...spread operator with html syntax (requires Twig 3.7.0 or higher)

## 2.9.0

- The `ComponentAttributes::defaults()` method now accepts any iterable argument.
Expand Down
7 changes: 7 additions & 0 deletions src/TwigComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,13 @@ normal ``{{ }}`` syntax:
// and pass object, or table, or anything you imagine
<twig:Alert :foo="['col' => ['foo', 'oof']]" />

To forward attributes to another component, use `{{...}}` spread operator syntax.
This requires Twig 3.7.0 or higher:

.. code-block:: html+twig

<twig:Alert{{ ...myAttributes }} />

Passing Blocks to your Component
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
35 changes: 35 additions & 0 deletions src/TwigComponent/src/AnonymousComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent;

use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;

/**
* @author Matheo Daninos <[email protected]>
*
* @internal
*/
final class AnonymousComponent
{
private array $props;

public function mount($props = []): void
{
$this->props = $props;
}

#[ExposeInTemplate(destruct: true)]
public function getProps(): array
{
return $this->props;
}
}
12 changes: 7 additions & 5 deletions src/TwigComponent/src/Attribute/ExposeInTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@
final class ExposeInTemplate
{
/**
* @param string|null $name The variable name to expose. Leave as null
* to default to property name.
* @param string|null $getter The getter method to use. Leave as null
* to default to PropertyAccessor logic.
* @param string|null $name The variable name to expose. Leave as null
* to default to property name.
* @param string|null $getter The getter method to use. Leave as null
* to default to PropertyAccessor logic.
* @param bool $destruct The content should be used as array of variable
* names
*/
public function __construct(public ?string $name = null, public ?string $getter = null)
public function __construct(public ?string $name = null, public ?string $getter = null, public bool $destruct = false)
{
}
}
27 changes: 25 additions & 2 deletions src/TwigComponent/src/ComponentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ final class ComponentFactory
* @param array<class-string, string> $classMap
*/
public function __construct(
private ComponentTemplateFinderInterface $componentTemplateFinder,
private ServiceLocator $components,
private PropertyAccessorInterface $propertyAccessor,
private EventDispatcherInterface $eventDispatcher,
private array $config,
private array $classMap,
private array $classMap
) {
}

Expand All @@ -43,6 +44,13 @@ public function metadataFor(string $name): ComponentMetadata
$name = $this->classMap[$name] ?? $name;

if (!$config = $this->config[$name] ?? null) {
if (($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) !== null) {
return new ComponentMetadata([
'key' => $name,
'template' => $template,
]);
}

$this->throwUnknownComponentException($name);
}

Expand Down Expand Up @@ -124,6 +132,12 @@ private function mount(object $component, array &$data): void
return;
}

if ($component instanceof AnonymousComponent) {
$component->mount($data);

return;
}

$parameters = [];

foreach ($method->getParameters() as $refParameter) {
Expand All @@ -149,6 +163,10 @@ private function getComponent(string $name): object
$name = $this->classMap[$name] ?? $name;

if (!$this->components->has($name)) {
if ($this->isAnonymousComponent($name)) {
return new AnonymousComponent();
}

$this->throwUnknownComponentException($name);
}

Expand Down Expand Up @@ -189,11 +207,16 @@ private function postMount(object $component, array $data): array
return $data;
}

private function isAnonymousComponent(string $name): bool
{
return null !== $this->componentTemplateFinder->findAnonymousComponentTemplate($name);
}

/**
* @return never
*/
private function throwUnknownComponentException(string $name): void
{
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config))));
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s. And no matching anonymous component template was found', $name, implode(', ', array_keys($this->config))));
}
}
14 changes: 14 additions & 0 deletions src/TwigComponent/src/ComponentRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ private function exposedVariables(object $component, bool $exposePublicProps): \
/** @var ExposeInTemplate $attribute */
$value = $attribute->getter ? $component->{rtrim($attribute->getter, '()')}() : $this->propertyAccessor->getValue($component, $property->name);

if ($attribute->destruct) {
foreach ($value as $key => $destructedValue) {
yield $key => $destructedValue;
}
}

yield $attribute->name ?? $property->name => $value;
}

Expand All @@ -148,6 +154,14 @@ private function exposedVariables(object $component, bool $exposePublicProps): \
throw new \LogicException(sprintf('Cannot use %s on methods with required parameters (%s::%s).', ExposeInTemplate::class, $component::class, $method->name));
}

if ($attribute->destruct) {
foreach ($component->{$method->name}() as $prop => $value) {
yield $prop => $value;
}

return;
}

yield $name => $component->{$method->name}();
}
}
Expand Down
49 changes: 49 additions & 0 deletions src/TwigComponent/src/ComponentTemplateFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent;

use Twig\Environment;

/**
* @author Matheo Daninos <[email protected]>
*/
final class ComponentTemplateFinder implements ComponentTemplateFinderInterface
{
public function __construct(
private Environment $environment
) {
}

public function findAnonymousComponentTemplate(string $name): ?string
{
$loader = $this->environment->getLoader();
$componentPath = rtrim(str_replace(':', '/', $name));

if ($loader->exists('components/'.$componentPath.'.html.twig')) {
return 'components/'.$componentPath.'.html.twig';
}

if ($loader->exists($componentPath.'.html.twig')) {
return $componentPath.'.html.twig';
}

if ($loader->exists('components/'.$componentPath)) {
return 'components/'.$componentPath;
}

if ($loader->exists($componentPath)) {
return $componentPath;
}

return null;
}
}
20 changes: 20 additions & 0 deletions src/TwigComponent/src/ComponentTemplateFinderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent;

/**
* @author Matheo Daninos <[email protected]>
*/
interface ComponentTemplateFinderInterface
{
public function findAnonymousComponentTemplate(string $name): ?string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public function process(ContainerBuilder $container): void
}

$factoryDefinition = $container->findDefinition('ux.twig_component.component_factory');
$factoryDefinition->setArgument(0, ServiceLocatorTagPass::register($container, $componentReferences));
$factoryDefinition->setArgument(3, $componentConfig);
$factoryDefinition->setArgument(4, $componentClassMap);
$factoryDefinition->setArgument(1, ServiceLocatorTagPass::register($container, $componentReferences));
$factoryDefinition->setArgument(4, $componentConfig);
$factoryDefinition->setArgument(5, $componentClassMap);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Symfony\UX\TwigComponent\ComponentRenderer;
use Symfony\UX\TwigComponent\ComponentRendererInterface;
use Symfony\UX\TwigComponent\ComponentStack;
use Symfony\UX\TwigComponent\ComponentTemplateFinder;
use Symfony\UX\TwigComponent\DependencyInjection\Compiler\TwigComponentPass;
use Symfony\UX\TwigComponent\Twig\ComponentExtension;
use Symfony\UX\TwigComponent\Twig\ComponentLexer;
Expand All @@ -40,6 +41,14 @@ public function load(array $configs, ContainerBuilder $container): void
throw new LogicException('The TwigBundle is not registered in your application. Try running "composer require symfony/twig-bundle".');
}

$container->register('ux.twig_component.component_template_finder', ComponentTemplateFinder::class)
->setArguments([
new Reference('twig'),
])
;

$container->setAlias(ComponentRendererInterface::class, 'ux.twig_component.component_renderer');

$container->registerAttributeForAutoconfiguration(
AsTwigComponent::class,
static function (ChildDefinition $definition, AsTwigComponent $attribute) {
Expand All @@ -49,6 +58,7 @@ static function (ChildDefinition $definition, AsTwigComponent $attribute) {

$container->register('ux.twig_component.component_factory', ComponentFactory::class)
->setArguments([
new Reference('ux.twig_component.component_template_finder'),
class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %s.', TwigComponentPass::class)) : null,
new Reference('property_accessor'),
new Reference('event_dispatcher'),
Expand All @@ -68,7 +78,7 @@ class_exists(AbstractArgument::class) ? new AbstractArgument(sprintf('Added in %
])
;

$container->setAlias(ComponentRendererInterface::class, 'ux.twig_component.component_renderer');
$container->register(ComponentTemplateFinder::class, 'ux.twig_component.component_template_finder');

$container->register('ux.twig_component.twig.component_extension', ComponentExtension::class)
->addTag('twig.extension')
Expand Down
1 change: 1 addition & 0 deletions src/TwigComponent/src/Twig/ComponentExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public function getTokenParsers(): array
{
return [
new ComponentTokenParser(fn () => $this->container->get(ComponentFactory::class)),
new PropsTokenParser(),
];
}

Expand Down
54 changes: 54 additions & 0 deletions src/TwigComponent/src/Twig/PropsNode.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\TwigComponent\Twig;

use Twig\Compiler;
use Twig\Node\Node;

/**
* @author Matheo Daninos <[email protected]>
*
* @internal
*/
class PropsNode extends Node
{
public function __construct(array $propsNames, array $values, $lineno = 0, string $tag = null)
{
parent::__construct($values, ['names' => $propsNames], $lineno, $tag);
}

public function compile(Compiler $compiler): void
{
foreach ($this->getAttribute('names') as $name) {
$compiler
->addDebugInfo($this)
->write('if (!isset($context[\''.$name.'\'])) {')
;

if (!$this->hasNode($name)) {
$compiler
->write('throw new \Twig\Error\RuntimeError("'.$name.' should be defined for component '.$this->getTemplateName().'");')
->write('}')
;

continue;
}

$compiler
->write('$context[\''.$name.'\'] = ')
->subcompile($this->getNode($name))
->raw(";\n")
->write('}')
;
}
}
}
Loading

0 comments on commit 8dad37e

Please sign in to comment.