Skip to content

Commit 19fe4a5

Browse files
committed
remove annotations
1 parent 1c401cd commit 19fe4a5

18 files changed

+334
-213
lines changed

src/LiveComponent/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"doctrine/doctrine-bundle": "^2.0",
4141
"doctrine/orm": "^2.7",
4242
"zenstruck/foundry": "^1.10",
43-
"zenstruck/browser": "^0.5.0"
43+
"zenstruck/browser": "^0.5.0",
44+
"doctrine/annotations": "^1.0"
4445
},
4546
"extra": {
4647
"branch-alias": {

src/LiveComponent/src/Attribute/AsLiveComponent.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,103 @@
2121
#[\Attribute(\Attribute::TARGET_CLASS)]
2222
final class AsLiveComponent extends AsTwigComponent
2323
{
24+
/**
25+
* @internal
26+
*
27+
* @param string|object $classOrObject
28+
*
29+
* @return LivePropContext[]
30+
*/
31+
public static function liveProps($classOrObject): \Traversable
32+
{
33+
foreach (self::propertiesFor($classOrObject) as $property) {
34+
if ($attribute = $property->getAttributes(LiveProp::class)[0] ?? null) {
35+
yield new LivePropContext($attribute->newInstance(), $property);
36+
}
37+
}
38+
}
39+
40+
/**
41+
* @internal
42+
*
43+
* @param string|object $classOrObject
44+
*/
45+
public static function isActionAllowed($classOrObject, string $action): bool
46+
{
47+
foreach (self::attributeMethodsFor(LiveAction::class, $classOrObject) as $method) {
48+
if ($action === $method->getName()) {
49+
return true;
50+
}
51+
}
52+
53+
return false;
54+
}
55+
56+
/**
57+
* @internal
58+
*
59+
* @param string|object $classOrObject
60+
*
61+
* @return \ReflectionMethod[]
62+
*/
63+
public static function beforeReRenderMethods($classOrObject): \Traversable
64+
{
65+
yield from self::attributeMethodsFor(BeforeReRender::class, $classOrObject);
66+
}
67+
68+
/**
69+
* @internal
70+
*
71+
* @param string|object $classOrObject
72+
*
73+
* @return \ReflectionMethod[]
74+
*/
75+
public static function postHydrateMethods($classOrObject): \Traversable
76+
{
77+
yield from self::attributeMethodsFor(PostHydrate::class, $classOrObject);
78+
}
79+
80+
/**
81+
* @internal
82+
*
83+
* @param string|object $classOrObject
84+
*
85+
* @return \ReflectionMethod[]
86+
*/
87+
public static function preDehydrateMethods($classOrObject): \Traversable
88+
{
89+
yield from self::attributeMethodsFor(PreDehydrate::class, $classOrObject);
90+
}
91+
92+
/**
93+
* @param string|object $classOrObject
94+
*
95+
* @return \ReflectionMethod[]
96+
*/
97+
private static function attributeMethodsFor(string $attribute, $classOrObject): \Traversable
98+
{
99+
foreach ((new \ReflectionClass($classOrObject))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
100+
if ($method->getAttributes($attribute)[0] ?? null) {
101+
yield $method;
102+
}
103+
}
104+
}
105+
106+
/**
107+
* @param string|object $classOrObject
108+
*
109+
* @return \ReflectionProperty[]
110+
*/
111+
private static function propertiesFor($classOrObject): \Traversable
112+
{
113+
$class = $classOrObject instanceof \ReflectionClass ? $classOrObject : new \ReflectionClass($classOrObject);
114+
115+
foreach ($class->getProperties() as $property) {
116+
yield $property;
117+
}
118+
119+
if ($parent = $class->getParentClass()) {
120+
yield from self::propertiesFor($parent);
121+
}
122+
}
24123
}

src/LiveComponent/src/Attribute/BeforeReRender.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@
1717
* This hook ONLY happens when rendering via HTTP: it does
1818
* not happen during the initial render of a component.
1919
*
20-
* @Annotation
21-
* @Target("METHOD")
22-
*
2320
* @experimental
2421
*/
22+
#[\Attribute(\Attribute::TARGET_METHOD)]
2523
final class BeforeReRender
2624
{
2725
}

src/LiveComponent/src/Attribute/LiveAction.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

1414
/**
15-
* @Annotation
16-
* @Target("METHOD")
17-
*
1815
* @experimental
1916
*/
17+
#[\Attribute(\Attribute::TARGET_METHOD)]
2018
final class LiveAction
2119
{
2220
}

src/LiveComponent/src/Attribute/LiveProp.php

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,19 @@
1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

1414
/**
15-
* @Annotation
16-
* @Target("PROPERTY")
17-
*
1815
* @experimental
1916
*/
17+
#[\Attribute(\Attribute::TARGET_PROPERTY)]
2018
final class LiveProp
2119
{
22-
/** @var bool */
23-
private $writable = false;
20+
private bool $writable;
2421

2522
/** @var string[] */
26-
private $exposed = [];
23+
private array $exposed;
2724

28-
/** @var string|null */
29-
private $hydrateWith = null;
25+
private ?string $hydrateWith;
3026

31-
/** @var string|null */
32-
private $dehydrateWith = null;
27+
private ?string $dehydrateWith;
3328

3429
/**
3530
*The "frontend" field name that should be used for this property.
@@ -39,22 +34,21 @@ final class LiveProp
3934
*
4035
* If you pass a string that ends in () - like "getFieldName()" - that
4136
* method on the component will be called to determine this.
42-
*
43-
* @var string|null
4437
*/
45-
private $fieldName = null;
46-
47-
public function __construct(array $values)
48-
{
49-
$validOptions = ['writable', 'exposed', 'hydrateWith', 'dehydrateWith', 'fieldName'];
50-
51-
foreach ($values as $name => $value) {
52-
if (!\in_array($name, $validOptions)) {
53-
throw new \InvalidArgumentException(sprintf('Unknown option "%s" passed to LiveProp. Valid options are: %s.', $name, implode(', ', $validOptions)));
54-
}
55-
56-
$this->$name = $value;
57-
}
38+
private ?string $fieldName;
39+
40+
public function __construct(
41+
bool $writable = false,
42+
array $exposed = [],
43+
?string $hydrateWith = null,
44+
?string $dehydrateWith = null,
45+
?string $fieldName = null
46+
) {
47+
$this->writable = $writable;
48+
$this->exposed = $exposed;
49+
$this->hydrateWith = $hydrateWith;
50+
$this->dehydrateWith = $dehydrateWith;
51+
$this->fieldName = $fieldName;
5852
}
5953

6054
public function isReadonly(): bool
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Attribute;
13+
14+
/**
15+
* @author Kevin Bond <[email protected]>
16+
*
17+
* @experimental
18+
*
19+
* @internal
20+
*/
21+
final class LivePropContext
22+
{
23+
private LiveProp $liveProp;
24+
private \ReflectionProperty $reflectionProperty;
25+
26+
public function __construct(LiveProp $liveProp, \ReflectionProperty $reflectionProperty)
27+
{
28+
$this->liveProp = $liveProp;
29+
$this->reflectionProperty = $reflectionProperty;
30+
}
31+
32+
public function liveProp(): LiveProp
33+
{
34+
return $this->liveProp;
35+
}
36+
37+
public function reflectionProperty(): \ReflectionProperty
38+
{
39+
return $this->reflectionProperty;
40+
}
41+
}

src/LiveComponent/src/Attribute/PostHydrate.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

1414
/**
15-
* @Annotation
16-
* @Target("METHOD")
17-
*
1815
* @experimental
1916
*/
17+
#[\Attribute(\Attribute::TARGET_METHOD)]
2018
final class PostHydrate
2119
{
2220
}

src/LiveComponent/src/Attribute/PreDehydrate.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
namespace Symfony\UX\LiveComponent\Attribute;
1313

1414
/**
15-
* @Annotation
16-
* @Target("METHOD")
17-
*
1815
* @experimental
1916
*/
17+
#[\Attribute(\Attribute::TARGET_METHOD)]
2018
final class PreDehydrate
2119
{
2220
}

src/LiveComponent/src/DependencyInjection/LiveComponentExtension.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ public function load(array $configs, ContainerBuilder $container): void
6565
->setArguments([
6666
new TaggedIteratorArgument('twig.component.property_hydrator'),
6767
new Reference('property_accessor'),
68-
new Reference('annotation_reader'),
6968
'%kernel.secret%',
7069
])
7170
;

src/LiveComponent/src/EventListener/LiveComponentSubscriber.php

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\UX\LiveComponent\EventListener;
1313

14-
use Doctrine\Common\Annotations\Reader;
1514
use Psr\Container\ContainerInterface;
1615
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
1716
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -29,7 +28,7 @@
2928
use Symfony\Component\Security\Csrf\CsrfToken;
3029
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
3130
use Symfony\Contracts\Service\ServiceSubscriberInterface;
32-
use Symfony\UX\LiveComponent\Attribute\BeforeReRender;
31+
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
3332
use Symfony\UX\LiveComponent\DefaultComponentController;
3433
use Symfony\UX\LiveComponent\LiveComponentHydrator;
3534
use Symfony\UX\TwigComponent\ComponentFactory;
@@ -61,7 +60,6 @@ public static function getSubscribedServices(): array
6160
ComponentFactory::class,
6261
ComponentRenderer::class,
6362
LiveComponentHydrator::class,
64-
Reader::class,
6563
'?'.CsrfTokenManagerInterface::class,
6664
];
6765
}
@@ -99,7 +97,7 @@ public function onKernelRequest(RequestEvent $event)
9997
throw new BadRequestHttpException('Invalid CSRF token.');
10098
}
10199

102-
if (!array_key_exists($componentName, $this->componentServiceMap)) {
100+
if (!\array_key_exists($componentName, $this->componentServiceMap)) {
103101
throw new NotFoundHttpException(sprintf('Component "%s" not found.', $componentName));
104102
}
105103

@@ -132,8 +130,8 @@ public function onKernelController(ControllerEvent $event)
132130
$component = $component->getComponent();
133131
}
134132

135-
if (null !== $action && !$this->container->get(LiveComponentHydrator::class)->isActionAllowed($component, $action)) {
136-
throw new NotFoundHttpException(sprintf('The action "%s" either doesn\'t exist or is not allowed in "%s". Make sure it exist and has the LiveProp attribute/annotation above it.', $action, \get_class($component)));
133+
if (null !== $action && !AsLiveComponent::isActionAllowed($component, $action)) {
134+
throw new NotFoundHttpException(sprintf('The action "%s" either doesn\'t exist or is not allowed in "%s". Make sure it exist and has the LiveAction attribute above it.', $action, \get_class($component)));
137135
}
138136

139137
$this->container->get(LiveComponentHydrator::class)->hydrate($component, $data);
@@ -214,7 +212,7 @@ public static function getSubscribedEvents()
214212

215213
private function createResponse(object $component, Request $request): Response
216214
{
217-
foreach ($this->beforeReRenderMethods($component) as $method) {
215+
foreach (AsLiveComponent::beforeReRenderMethods($component) as $method) {
218216
$component->{$method->name}();
219217
}
220218

@@ -243,16 +241,4 @@ private function isLiveComponentJsonRequest(Request $request): bool
243241
{
244242
return \in_array($request->getPreferredFormat(), [self::JSON_FORMAT, 'json'], true);
245243
}
246-
247-
/**
248-
* @return \ReflectionMethod[]
249-
*/
250-
private function beforeReRenderMethods(object $component): iterable
251-
{
252-
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
253-
if ($this->container->get(Reader::class)->getMethodAnnotation($method, BeforeReRender::class)) {
254-
yield $method;
255-
}
256-
}
257-
}
258244
}

0 commit comments

Comments
 (0)