Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: register component classes with custom PHP attribute #33

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\ContainerBuilding\Attribute;

#[\Attribute(\Attribute::TARGET_CLASS)]
class ComponentForContentElements
{
public const TAG_NAME = 'webcomponents.forContentElements';

public function __construct(
public readonly string $cType,
) {}
}
48 changes: 48 additions & 0 deletions Classes/ContainerBuilding/CompilerPass/ComponentPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\ContainerBuilding\CompilerPass;

use Sinso\Webcomponents\ContainerBuilding\ComponentRegistry;
use Sinso\Webcomponents\ContainerBuilding\ComponentRegistryEntry;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ComponentPass implements CompilerPassInterface
{
public function __construct(
private readonly string $tagName
) {}

public function process(ContainerBuilder $container): void
{
$componentRegistryEntries = $this->collectComponentRegistryEntries($container);
$componentRegistryDefinition = $container->findDefinition(ComponentRegistry::class);
foreach ($componentRegistryEntries as $componentRegistryEntry) {
$componentRegistryDefinition->addMethodCall(
'registerComponent',
[
$componentRegistryEntry->componentClassname,
$componentRegistryEntry->getCTypes(),
]
);
}
}

/**
* @return iterable<ComponentRegistryEntry>
*/
private function collectComponentRegistryEntries(ContainerBuilder $container): iterable
{
$componentClasses = [];
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
$componentRegistryEntry = new ComponentRegistryEntry($serviceName);
foreach ($tags as $attribute) {
$componentRegistryEntry->addCType($attribute['cType']);
}
$componentClasses[] = $componentRegistryEntry;
}
return $componentClasses;
}
}
33 changes: 33 additions & 0 deletions Classes/ContainerBuilding/ComponentRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\ContainerBuilding;

class ComponentRegistry
{
/**
* @var array<ComponentRegistryEntry>
*/
private array $componentRegistryEntries = [];

/**
* @param iterable<int, string> $cTypes
*/
public function registerComponent(string $serviceClassName, iterable $cTypes): void
{
$componentRegistryEntry = new ComponentRegistryEntry($serviceClassName);
foreach ($cTypes as $cType) {
$componentRegistryEntry->addCType($cType);
}
$this->componentRegistryEntries[] = $componentRegistryEntry;
}

/**
* @return iterable<ComponentRegistryEntry>
*/
public function getComponentRegistryEntries(): iterable
{
return $this->componentRegistryEntries;
}
}
30 changes: 30 additions & 0 deletions Classes/ContainerBuilding/ComponentRegistryEntry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\ContainerBuilding;

class ComponentRegistryEntry
{
/**
* @var array<string> $cTypes
*/
private array $cTypes = [];

public function __construct(
public readonly string $componentClassname,
) {}

public function addCType(string $cType): void
{
$this->cTypes[] = $cType;
}

/**
* @return iterable<string>
*/
public function getCTypes(): iterable
{
return $this->cTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Sinso\Webcomponents\ContainerBuilding\EventListener;

use Sinso\Webcomponents\ContainerBuilding\ComponentRegistry;
use Sinso\Webcomponents\ContainerBuilding\ComponentRegistryEntry;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Core\Event\BootCompletedEvent;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;

#[AsEventListener(identifier: 'webcomponents/addComponentsToTypoScript')]
class AddComponentsToTypoScript
{
public function __invoke(BootCompletedEvent $event): void
{
ExtensionManagementUtility::addTypoScriptSetup($this->generateTypoScript());
}

public function __construct(
private readonly ComponentRegistry $componentRegistry,
) {}

private function generateTypoScript(): string
{
$typoScript = '';
foreach ($this->componentRegistry->getComponentRegistryEntries() as $entry) {
$typoScript .= $this->generateTypoScriptForComponent($entry);
}
return $typoScript;
}

private function generateTypoScriptForComponent(ComponentRegistryEntry $entry): string
{
$typoScript = '';
foreach ($entry->getCTypes() as $cType) {
$typoScript .= $this->generateTypoScriptForCType($entry, $cType);
}
return $typoScript;
}

private function generateTypoScriptForCType(ComponentRegistryEntry $entry, string $cType): string
{
return <<<TYPOSCRIPT
tt_content.{$cType} >
tt_content.{$cType} = WEBCOMPONENT
tt_content.{$cType}.component = {$entry->componentClassname}
TYPOSCRIPT;
}
}
21 changes: 21 additions & 0 deletions Configuration/Services.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use Sinso\Webcomponents\ContainerBuilding\Attribute\ComponentForContentElements;
use Sinso\Webcomponents\ContainerBuilding\CompilerPass\ComponentPass;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
$containerBuilder->registerAttributeForAutoconfiguration(
ComponentForContentElements::class,
static function (ChildDefinition $definition, ComponentForContentElements $attribute, \Reflector $reflector): void {
$definition->addTag(
ComponentForContentElements::TAG_NAME,
['cType' => $attribute->cType]
);
}
);

$containerBuilder->addCompilerPass(new ComponentPass(ComponentForContentElements::TAG_NAME));
};
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,17 @@ Generates the output:

You can populate the web component with PHP:

```
tt_content.tx_myext_mycontentelement = WEBCOMPONENT
tt_content.tx_myext_mycontentelement.component = Acme\MyExt\Components\MyContentElement
```

```php
<?php

namespace Acme\MyExt\Components;

use Sinso\Webcomponents\ContainerBuilding\Attribute\ComponentForContentElements;
use Sinso\Webcomponents\DataProviding\ComponentInterface;
use Sinso\Webcomponents\DataProviding\Traits\ContentObjectRendererTrait;
use Sinso\Webcomponents\Dto\ComponentRenderingData;

#[ComponentForContentElements(cType: 'tx_myext_my-content-element')]
class MyContentElement implements ComponentInterface
{
use ContentObjectRendererTrait;
Expand All @@ -60,6 +57,8 @@ class MyContentElement implements ComponentInterface
}
```

The `#[ComponentForContentElements]` attribute will take care of setting up the necessary TypoScript in the background.

## Abort rendering

The component classes can use the `\Sinso\Webcomponents\DataProviding\Traits\Assert` trait to abort rendering, for example if the record is not available:
Expand All @@ -69,13 +68,15 @@ The component classes can use the `\Sinso\Webcomponents\DataProviding\Traits\Ass

namespace Acme\MyExt\Components;

use Sinso\Webcomponents\ContainerBuilding\Attribute\ComponentForContentElements;
use Sinso\Webcomponents\DataProviding\ComponentInterface;
use Sinso\Webcomponents\DataProviding\Traits\Assert;
use Sinso\Webcomponents\DataProviding\Traits\ContentObjectRendererTrait;
use Sinso\Webcomponents\DataProviding\Traits\FileReferences;
use Sinso\Webcomponents\Dto\ComponentRenderingData;
use TYPO3\CMS\Core\Resource\FileReference;

#[ComponentForContentElements(cType: 'image')]
class Image implements ComponentInterface
{
use Assert;
Expand Down
Loading