forked from symfony/ux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request symfony#1 from nicolas-grekas/turbo
Tweaks and review
- Loading branch information
Showing
17 changed files
with
167 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,36 +14,37 @@ | |
use Doctrine\Common\EventArgs; | ||
use Doctrine\ORM\Event\OnFlushEventArgs; | ||
use Symfony\Contracts\Service\ResetInterface; | ||
use Symfony\UX\Turbo\Broadcast; | ||
use Symfony\UX\Turbo\Attribute\Broadcast; | ||
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface; | ||
|
||
/** | ||
* Detects changes made from Doctrine entities and broadcasts updates to the Mercure hub. | ||
* | ||
* @author Kévin Dunglas <[email protected]> | ||
* | ||
* @see https://github.com/api-platform/core/blob/master/src/Bridge/Doctrine/EventListener/PublishMercureUpdatesListener.php Adapted from API Platform. | ||
* | ||
* @todo backport MongoDB support | ||
* | ||
* @experimental | ||
*/ | ||
final class BroadcastListener implements ResetInterface | ||
{ | ||
private $broadcaster; | ||
|
||
/** | ||
* @var array<class-string, \ReflectionAttribute[]> | ||
*/ | ||
private $broadcastedClasses; | ||
|
||
/** | ||
* @var \SplObjectStorage<object, object> | ||
*/ | ||
private \SplObjectStorage $createdEntities; | ||
private $createdEntities; | ||
/** | ||
* @var \SplObjectStorage<object, object> | ||
*/ | ||
private \SplObjectStorage $updatedEntities; | ||
private $updatedEntities; | ||
/** | ||
* @var \SplObjectStorage<object, object> | ||
*/ | ||
private \SplObjectStorage $removedEntities; | ||
private $removedEntities; | ||
|
||
public function __construct(BroadcasterInterface $broadcaster) | ||
{ | ||
|
@@ -110,7 +111,11 @@ public function reset(): void | |
|
||
private function storeEntitiesToPublish(object $entity, string $property): void | ||
{ | ||
if ((new \ReflectionClass($entity))->getAttributes(Broadcast::class)) { | ||
$class = \get_class($entity); | ||
|
||
$this->broadcastedClasses[$class] ?? $this->broadcastedClasses[$class] = (new \ReflectionClass($class))->getAttributes(Broadcast::class); | ||
|
||
if ($this->broadcastedClasses[$class]) { | ||
$this->{$property}->attach('removedEntities' === $property ? clone $entity : $entity); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,8 +15,7 @@ | |
use Symfony\Component\Mercure\Update; | ||
use Symfony\Component\PropertyAccess\PropertyAccess; | ||
use Symfony\Component\PropertyAccess\PropertyAccessorInterface; | ||
use Symfony\Component\Security\Core\Authorization\ExpressionLanguage; | ||
use Symfony\UX\Turbo\Broadcast; | ||
use Symfony\UX\Turbo\Attribute\Broadcast; | ||
use Symfony\UX\Turbo\Broadcaster\BroadcasterInterface; | ||
use Twig\Environment; | ||
|
||
|
@@ -25,16 +24,13 @@ | |
* | ||
* Supported options are: | ||
* | ||
* * topics (string[]) Mercure topics to use, defaults to an array containing the Fully Qualified Class Name with "\" characters replaced by ":" characters. | ||
* * createTemplate (string) The Twig template to render when a new object is created | ||
* * updateTemplate (string) The Twig template to render when a new object is updated | ||
* * removeTemplate (string) The Twig template to render when a new object is removed | ||
* * private (bool) Marks Mercure updates as private | ||
* * id (string) ID field of the SSE | ||
* * type (string) type field of the SSE | ||
* * retry (int) retry field of the SSE | ||
* | ||
* The options can also be generated using the ExpressionLanguage language: if the option is a string, it is evaluated as an expression that must return an array. | ||
* * transports (string[]) The name of the transports to broadcast to | ||
* * topics (string[]) The topics to use; the default topic is derived from the FQCN of the entity and from its id | ||
* * template (string) The Twig template to render when a new object is created, updated or removed | ||
* * private (bool) Marks Mercure updates as private | ||
* * id (string) ID field of the SSE | ||
* * type (string) type field of the SSE | ||
* * retry (int) retry field of the SSE | ||
* | ||
* @author Kévin Dunglas <[email protected]> | ||
* | ||
|
@@ -51,12 +47,7 @@ final class Broadcaster implements BroadcasterInterface | |
private $twig; | ||
private $hub; | ||
private $propertyAccessor; | ||
private $entityNamespace; | ||
|
||
/** | ||
* @var ExpressionLanguage|null | ||
*/ | ||
private $expressionLanguage; | ||
private $templatePrefixes; | ||
|
||
private const OPTIONS = [ | ||
// Generic options | ||
|
@@ -71,14 +62,11 @@ final class Broadcaster implements BroadcasterInterface | |
'retry', | ||
]; | ||
|
||
public function __construct( | ||
string $name, | ||
Environment $twig, | ||
HubInterface $hub, | ||
?PropertyAccessorInterface $propertyAccessor, | ||
?ExpressionLanguage $expressionLanguage = null, | ||
?string $entityNamespace = null | ||
) { | ||
/** | ||
* @param array<string, string> $templatePrefixes | ||
*/ | ||
public function __construct(string $name, Environment $twig, HubInterface $hub, ?PropertyAccessorInterface $propertyAccessor, array $templatePrefixes = []) | ||
{ | ||
if (80000 > \PHP_VERSION_ID) { | ||
throw new \LogicException('The broadcast feature requires PHP 8.0 or greater, you must either upgrade to PHP 8 or disable it.'); | ||
} | ||
|
@@ -87,13 +75,7 @@ public function __construct( | |
$this->twig = $twig; | ||
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); | ||
$this->hub = $hub; | ||
$this->entityNamespace = $entityNamespace; | ||
|
||
if ($expressionLanguage) { | ||
$this->expressionLanguage = $expressionLanguage; | ||
} elseif (class_exists(ExpressionLanguage::class)) { | ||
$this->expressionLanguage = new ExpressionLanguage(); | ||
} | ||
$this->templatePrefixes = $templatePrefixes; | ||
} | ||
|
||
public function broadcast(object $entity, string $action): void | ||
|
@@ -108,11 +90,11 @@ public function broadcast(object $entity, string $action): void | |
$broadcast = $attribute->newInstance(); | ||
$options = $this->normalizeOptions($entity, $action, $broadcast->options); | ||
|
||
if (isset($options['transports']) && !\in_array($this->name, $options['transports'], false)) { | ||
if (isset($options['transports']) && !\in_array($this->name, $options['transports'], true)) { | ||
return; | ||
} | ||
|
||
// What must we do if the template or the block doesn't exist? Throwing for now. | ||
// Will throw if the template or the block doesn't exist | ||
$data = $this->twig->load($options['template'])->renderBlock($action, ['entity' => $entity, 'action' => $action, 'options' => $options]); | ||
|
||
$update = new Update( | ||
|
@@ -138,14 +120,6 @@ private function normalizeOptions(object $entity, string $action, array $options | |
$options['transports'] = (array) $options['transports']; | ||
} | ||
|
||
if (\is_string($options[0] ?? null)) { | ||
if (null === $this->expressionLanguage) { | ||
throw new \RuntimeException('The Expression Language component is not installed. Try running "composer require symfony/expression-language".'); | ||
} | ||
|
||
$options = $this->expressionLanguage->evaluate($options[0], ['entity' => $entity, 'action' => $action]); | ||
} | ||
|
||
$entityClass = \get_class($entity); | ||
|
||
if ($extraKeys = array_diff(array_keys($options), self::OPTIONS)) { | ||
|
@@ -157,12 +131,15 @@ private function normalizeOptions(object $entity, string $action, array $options | |
return $options; | ||
} | ||
|
||
$dir = $entityClass; | ||
if ($this->entityNamespace && 0 === strpos($entityClass, $this->entityNamespace)) { | ||
$dir = substr($entityClass, \strlen($this->entityNamespace)); | ||
$file = $entityClass; | ||
foreach ($this->templatePrefixes as $namespace => $prefix) { | ||
if (0 === strpos($entityClass, $namespace)) { | ||
$file = substr_replace($entityClass, $prefix, 0, \strlen($namespace)); | ||
break; | ||
} | ||
} | ||
|
||
$options['template'] = sprintf('broadcast/%s.stream.html.twig', str_replace('\\', '/', $dir)); | ||
$options['template'] = str_replace('\\', '/', $file).'.stream.html.twig'; | ||
|
||
return $options; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.