4
4
namespace TheWebSolver \Codegarage \Cli ;
5
5
6
6
use Countable ;
7
+ use LogicException ;
8
+ use Psr \Container \ContainerInterface ;
7
9
use TheWebSolver \Codegarage \Cli \Console ;
8
10
use TheWebSolver \Codegarage \Cli \Data \EventTask ;
9
- use TheWebSolver \Codegarage \Container \Container ;
10
11
use TheWebSolver \Codegarage \Cli \Event \AfterLoadEvent ;
11
12
use Symfony \Component \EventDispatcher \EventDispatcher ;
12
13
use TheWebSolver \Codegarage \Cli \Event \CommandSubscriber ;
@@ -19,23 +20,18 @@ class CommandLoader implements Countable {
19
20
/** @var array{dirpath:string,namespace:string} */
20
21
protected array $ base ;
21
22
private bool $ scanStarted ;
23
+ private ContainerInterface $ container ;
22
24
23
25
/**
24
- * @param Container $container
25
26
* @param array<int,array<string,string>> $namespacedDirectory
26
27
* @param array<string,class-string<Console>> $commands
27
28
*/
28
29
final private function __construct (
29
- private Container $ container ,
30
30
private array $ namespacedDirectory = array (),
31
31
private array $ commands = array (),
32
32
private ?EventDispatcher $ dispatcher = null
33
33
) {}
34
34
35
- public function getContainer (): Container {
36
- return $ this ->container ;
37
- }
38
-
39
35
/** @return array<int,array<string,string>> List of directory path indexed by its namespace. */
40
36
public function getNamespacedDirectories (): array {
41
37
return $ this ->namespacedDirectory ;
@@ -46,22 +42,22 @@ public function getCommands(): array {
46
42
return $ this ->commands ;
47
43
}
48
44
49
- public static function with ( ? Container $ container ): static {
50
- return self ::getInstance ( $ container );
45
+ public static function start ( ): static {
46
+ return self ::getInstance ();
51
47
}
52
48
53
49
/** @param callable(EventTask): void $listener */
54
- public static function withEvent ( callable $ listener, ? Container $ container = null ): static {
55
- return self ::getInstance ( $ container , event: true )->withListener ( $ listener );
50
+ public static function withEvent ( callable $ listener ): static {
51
+ return self ::getInstance ( event: true )->withListener ( $ listener );
56
52
}
57
53
58
54
/** @param array<int,array<string,string>> $namespacedDirectories */
59
- public static function loadCommands ( array $ namespacedDirectories , ? Container $ container = null ): static {
60
- $ loader = self ::getInstance ( $ container );
55
+ public static function loadCommands ( array $ namespacedDirectories , ContainerInterface $ container ): static {
56
+ $ loader = self ::getInstance ();
61
57
62
58
$ loader ->namespacedDirectory = $ namespacedDirectories ;
63
59
64
- return $ loader ->load ();
60
+ return $ loader ->load ( $ container );
65
61
}
66
62
67
63
public function inDirectory ( string $ path , string $ namespace ): static {
@@ -70,18 +66,55 @@ public function inDirectory( string $path, string $namespace ): static {
70
66
return $ this ;
71
67
}
72
68
73
- public function load (): static {
69
+ final public function load ( ContainerInterface $ container ): static {
74
70
if ( $ this ->scanStarted ?? false ) {
75
71
return $ this ;
76
72
}
77
73
78
74
$ this ->scanStarted = true ;
75
+ $ this ->container = $ container ;
79
76
80
- $ this ->container ->get ( Cli::class )->eventDispatcher ()->addSubscriber ( new CommandSubscriber () );
77
+ $ this ->getApp ()->eventDispatcher ()->addSubscriber ( new CommandSubscriber () );
78
+
79
+ $ this ->initialize ();
81
80
82
81
return $ this ->startScan ();
83
82
}
84
83
84
+ /**
85
+ * Allows inheriting class to initialize its actions before scan has started.
86
+ */
87
+ protected function initialize (): void {}
88
+
89
+ /**
90
+ * Returns an instance of Console application (Cli by default).
91
+ *
92
+ * This may throw PSR-11 exceptions if no container binding identifier exists with $id as `Cli::class`.
93
+ *
94
+ * @throws LogicException When container resolves something other than `Cli` or its inheritance.
95
+ */
96
+ final protected function getApp (): Cli {
97
+ return ( $ app = $ this ->container ->get ( Cli::class ) ) instanceof Cli
98
+ ? $ app
99
+ : throw new LogicException ( 'Impossible to start Cli application using container. ' );
100
+ }
101
+
102
+ /**
103
+ * Allows inheriting class to handle the found command.
104
+ *
105
+ * By default, it defers command instantiation until current command is ran. The provided container
106
+ * must have `ContainerInterface::set()` method to defer command instantiation. If that is not the
107
+ * case, then this method must be overridden to handle found command (preferably defer loading).
108
+ *
109
+ * @param class-string<Console> $classname
110
+ * @param callable(ContainerInterface): void $command
111
+ * @link https://symfony.com/doc/current/console/lazy_commands.html
112
+ */
113
+ protected function useFoundCommand ( string $ classname , callable $ command , string $ commandName ): void {
114
+ /** @disregard P1013 Undefined method 'set' */
115
+ method_exists ( $ this ->container , 'set ' ) && $ this ->container ->set ( $ classname , $ command );
116
+ }
117
+
85
118
protected function getRootPath (): string {
86
119
return $ this ->base ['dirpath ' ];
87
120
}
@@ -99,29 +132,16 @@ protected function forCurrentFile(): void {
99
132
$ commandName = $ commandClass ::asCommandName ();
100
133
$ this ->commands [ $ commandName ] = $ commandClass ;
101
134
102
- $ this ->handleResolved ( $ commandClass , $ lazyload , $ commandName );
135
+ $ this ->useFoundCommand ( $ commandClass , $ lazyload , $ commandName );
103
136
104
137
// Allow developers to listen for resolved command by Command Loader with the "EventTask".
105
138
if ( $ commandRunner = $ this ->getCommandRunnerFromEvent () ) {
106
139
$ commandRunner ( new EventTask ( $ lazyload ( ... ), $ commandClass , $ commandName , $ this ->container ) );
107
140
}
108
141
}
109
142
110
- /**
111
- * Allows developers to handle the resolved command.
112
- *
113
- * By default, it defers command instantiation until current command is ran.
114
- *
115
- * @param class-string<Console> $classname
116
- * @param callable(Container): void $command
117
- * @link https://symfony.com/doc/current/console/lazy_commands.html
118
- */
119
- protected function handleResolved ( string $ classname , callable $ command , string $ commandName ): void {
120
- $ this ->container ->set ( $ classname , $ command );
121
- }
122
-
123
- private static function getInstance ( ?Container $ container , bool $ event = false ): static {
124
- return new static ( $ container ??= Container::boot (), dispatcher: $ event ? new EventDispatcher () : null );
143
+ private static function getInstance ( bool $ event = false ): static {
144
+ return new static ( dispatcher: $ event ? new EventDispatcher () : null );
125
145
}
126
146
127
147
private function fromFilePathToPsr4SpecificationFullyQualifiedClassName (): ?string {
@@ -143,9 +163,7 @@ private function withListener( callable $listener ): static {
143
163
private function startScan (): static {
144
164
array_walk ( $ this ->namespacedDirectory , $ this ->scanBaseDirectory ( ... ) );
145
165
146
- $ this ->container ->get ( Cli::class )->setCommandLoader (
147
- new ContainerCommandLoader ( $ this ->container , $ this ->commands )
148
- );
166
+ $ this ->getApp ()->setCommandLoader ( new ContainerCommandLoader ( $ this ->container , $ this ->commands ) );
149
167
150
168
return $ this ;
151
169
}
0 commit comments