Skip to content

Commit d609dc7

Browse files
committed
[Workflow] Add some PHP attributes to register listeners and guards
1 parent 1e68f14 commit d609dc7

10 files changed

+368
-0
lines changed

Attribute/AsAnnounceListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsAnnounceListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $transition = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('announce', 'transition', $workflow, $transition), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsCompletedListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsCompletedListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $transition = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('completed', 'transition', $workflow, $transition), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsEnterListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsEnterListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $place = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('enter', 'place', $workflow, $place), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsEnteredListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsEnteredListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $place = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('entered', 'place', $workflow, $place), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsGuardListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsGuardListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $transition = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('guard', 'transition', $workflow, $transition), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsLeaveListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsLeaveListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $place = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('leave', 'place', $workflow, $place), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/AsTransitionListener.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*/
19+
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
20+
final class AsTransitionListener extends AsEventListener
21+
{
22+
use BuildEventNameTrait;
23+
24+
public function __construct(
25+
string $workflow = null,
26+
string $transition = null,
27+
string $method = null,
28+
int $priority = 0,
29+
string $dispatcher = null,
30+
) {
31+
parent::__construct($this->buildEventName('transition', 'transition', $workflow, $transition), $method, $priority, $dispatcher);
32+
}
33+
}

Attribute/BuildEventNameTrait.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Attribute;
13+
14+
use Symfony\Component\Workflow\Exception\LogicException;
15+
16+
/**
17+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
18+
*
19+
* @internal
20+
*/
21+
trait BuildEventNameTrait
22+
{
23+
private static function buildEventName(string $keyword, string $argument, string $workflow = null, string $node = null): string
24+
{
25+
if (null === $workflow) {
26+
if (null !== $node) {
27+
throw new LogicException(sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class));
28+
}
29+
30+
return sprintf('workflow.%s', $keyword);
31+
}
32+
33+
if (null === $node) {
34+
return sprintf('workflow.%s.%s', $workflow, $keyword);
35+
}
36+
37+
return sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node);
38+
}
39+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* Add support for storing marking in a property
1010
* Add a profiler
1111
* Add support for multiline descriptions in PlantUML diagrams
12+
* Add PHP attributes to register listeners and guards
1213

1314
6.2
1415
---

Tests/Attribute/AsListenerTest.php

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Component\Workflow\Tests\Attribute;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Workflow\Attribute;
16+
use Symfony\Component\Workflow\Exception\LogicException;
17+
18+
class AsListenerTest extends TestCase
19+
{
20+
/**
21+
* @dataProvider provideOkTests
22+
*/
23+
public function testOk(string $class, string $expectedEvent, string $workflow = null, string $node = null)
24+
{
25+
$attribute = new $class($workflow, $node);
26+
27+
$this->assertSame($expectedEvent, $attribute->event);
28+
}
29+
30+
public static function provideOkTests(): iterable
31+
{
32+
yield [Attribute\AsAnnounceListener::class, 'workflow.announce'];
33+
yield [Attribute\AsAnnounceListener::class, 'workflow.w.announce', 'w'];
34+
yield [Attribute\AsAnnounceListener::class, 'workflow.w.announce.n', 'w', 'n'];
35+
36+
yield [Attribute\AsCompletedListener::class, 'workflow.completed'];
37+
yield [Attribute\AsCompletedListener::class, 'workflow.w.completed', 'w'];
38+
yield [Attribute\AsCompletedListener::class, 'workflow.w.completed.n', 'w', 'n'];
39+
40+
yield [Attribute\AsEnterListener::class, 'workflow.enter'];
41+
yield [Attribute\AsEnterListener::class, 'workflow.w.enter', 'w'];
42+
yield [Attribute\AsEnterListener::class, 'workflow.w.enter.n', 'w', 'n'];
43+
44+
yield [Attribute\AsEnteredListener::class, 'workflow.entered'];
45+
yield [Attribute\AsEnteredListener::class, 'workflow.w.entered', 'w'];
46+
yield [Attribute\AsEnteredListener::class, 'workflow.w.entered.n', 'w', 'n'];
47+
48+
yield [Attribute\AsGuardListener::class, 'workflow.guard'];
49+
yield [Attribute\AsGuardListener::class, 'workflow.w.guard', 'w'];
50+
yield [Attribute\AsGuardListener::class, 'workflow.w.guard.n', 'w', 'n'];
51+
52+
yield [Attribute\AsLeaveListener::class, 'workflow.leave'];
53+
yield [Attribute\AsLeaveListener::class, 'workflow.w.leave', 'w'];
54+
yield [Attribute\AsLeaveListener::class, 'workflow.w.leave.n', 'w', 'n'];
55+
56+
yield [Attribute\AsTransitionListener::class, 'workflow.transition'];
57+
yield [Attribute\AsTransitionListener::class, 'workflow.w.transition', 'w'];
58+
yield [Attribute\AsTransitionListener::class, 'workflow.w.transition.n', 'w', 'n'];
59+
}
60+
61+
/**
62+
* @dataProvider provideTransitionThrowException
63+
*/
64+
public function testTransitionThrowException(string $class)
65+
{
66+
$this->expectException(LogicException::class);
67+
$this->expectExceptionMessage(sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class));
68+
69+
new $class(transition: 'some');
70+
}
71+
72+
public static function provideTransitionThrowException(): iterable
73+
{
74+
yield [Attribute\AsAnnounceListener::class, 'workflow.announce'];
75+
yield [Attribute\AsCompletedListener::class, 'workflow.completed'];
76+
yield [Attribute\AsGuardListener::class, 'workflow.guard'];
77+
yield [Attribute\AsTransitionListener::class, 'workflow.transition'];
78+
}
79+
80+
/**
81+
* @dataProvider providePlaceThrowException
82+
*/
83+
public function testPlaceThrowException(string $class)
84+
{
85+
$this->expectException(LogicException::class);
86+
$this->expectExceptionMessage(sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class));
87+
88+
new $class(place: 'some');
89+
}
90+
91+
public static function providePlaceThrowException(): iterable
92+
{
93+
yield [Attribute\AsEnteredListener::class, 'workflow.entered'];
94+
yield [Attribute\AsEnterListener::class, 'workflow.enter'];
95+
yield [Attribute\AsLeaveListener::class, 'workflow.leave'];
96+
}
97+
}

0 commit comments

Comments
 (0)