1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Yiisoft\Yii\Runner\RoadRunner; |
||||||
6 | |||||||
7 | use ErrorException; |
||||||
8 | use Exception; |
||||||
9 | use JsonException; |
||||||
10 | use Psr\Container\ContainerExceptionInterface; |
||||||
11 | use Psr\Container\ContainerInterface; |
||||||
12 | use Psr\Container\NotFoundExceptionInterface; |
||||||
13 | use Psr\Http\Message\ResponseInterface; |
||||||
14 | use ReflectionClass; |
||||||
15 | use RuntimeException; |
||||||
16 | use Spiral\RoadRunner\Environment; |
||||||
17 | use Spiral\RoadRunner\Environment\Mode; |
||||||
18 | use Spiral\RoadRunner\Http\PSR7WorkerInterface; |
||||||
19 | use Temporal\Worker\Transport\HostConnectionInterface; |
||||||
20 | use Temporal\Worker\WorkerFactoryInterface; |
||||||
21 | use Throwable; |
||||||
22 | use Yiisoft\Definitions\Exception\CircularReferenceException; |
||||||
23 | use Yiisoft\Definitions\Exception\InvalidConfigException; |
||||||
24 | use Yiisoft\Definitions\Exception\NotInstantiableException; |
||||||
25 | use Yiisoft\Di\NotFoundException; |
||||||
26 | use Yiisoft\Di\StateResetter; |
||||||
27 | use Yiisoft\ErrorHandler\ErrorHandler; |
||||||
0 ignored issues
–
show
|
|||||||
28 | use Yiisoft\ErrorHandler\Renderer\HtmlRenderer; |
||||||
29 | use Yiisoft\Log\Logger; |
||||||
30 | use Yiisoft\Log\Target\File\FileTarget; |
||||||
31 | use Yiisoft\Yii\Http\Application; |
||||||
32 | use Yiisoft\Yii\Runner\ApplicationRunner; |
||||||
33 | use Yiisoft\Yii\Runner\RoadRunner\Temporal\TemporalDeclarationProvider; |
||||||
34 | |||||||
35 | use function gc_collect_cycles; |
||||||
36 | use function interface_exists; |
||||||
37 | |||||||
38 | /** |
||||||
39 | * `RoadRunnerHttpApplicationRunner` runs the Yii HTTP application using RoadRunner. |
||||||
40 | */ |
||||||
41 | final class RoadRunnerHttpApplicationRunner extends ApplicationRunner |
||||||
42 | { |
||||||
43 | private ?ErrorHandler $temporaryErrorHandler = null; |
||||||
44 | private ?PSR7WorkerInterface $psr7Worker = null; |
||||||
45 | private bool $isTemporalEnabled = false; |
||||||
46 | |||||||
47 | /** |
||||||
48 | * @param string $rootPath The absolute path to the project root. |
||||||
49 | * @param bool $debug Whether the debug mode is enabled. |
||||||
50 | * @param bool $checkEvents Whether to check events' configuration. |
||||||
51 | * @param string|null $environment The environment name. |
||||||
52 | * @param string $bootstrapGroup The bootstrap configuration group name. |
||||||
53 | * @param string $eventsGroup The events' configuration group name. |
||||||
54 | * @param string $diGroup The container definitions' configuration group name. |
||||||
55 | * @param string $diProvidersGroup The container providers' configuration group name. |
||||||
56 | * @param string $diDelegatesGroup The container delegates' configuration group name. |
||||||
57 | * @param string $diTagsGroup The container tags' configuration group name. |
||||||
58 | * @param string $paramsGroup The configuration parameters group name. |
||||||
59 | * @param array $nestedParamsGroups Configuration group names that are included into configuration parameters group. |
||||||
60 | * This is needed for recursive merging of parameters. |
||||||
61 | * @param array $nestedEventsGroups Configuration group names that are included into events' configuration group. |
||||||
62 | * This is needed for reverse and recursive merge of events' configurations. |
||||||
63 | * |
||||||
64 | * @psalm-param list<string> $nestedParamsGroups |
||||||
65 | * @psalm-param list<string> $nestedEventsGroups |
||||||
66 | */ |
||||||
67 | 12 | public function __construct( |
|||||
68 | string $rootPath, |
||||||
69 | bool $debug = false, |
||||||
70 | bool $checkEvents = false, |
||||||
71 | ?string $environment = null, |
||||||
72 | string $bootstrapGroup = 'bootstrap-web', |
||||||
73 | string $eventsGroup = 'events-web', |
||||||
74 | string $diGroup = 'di-web', |
||||||
75 | string $diProvidersGroup = 'di-providers-web', |
||||||
76 | string $diDelegatesGroup = 'di-delegates-web', |
||||||
77 | string $diTagsGroup = 'di-tags-web', |
||||||
78 | string $paramsGroup = 'params-web', |
||||||
79 | array $nestedParamsGroups = ['params'], |
||||||
80 | array $nestedEventsGroups = ['events'], |
||||||
81 | ) { |
||||||
82 | 12 | parent::__construct( |
|||||
83 | 12 | $rootPath, |
|||||
84 | 12 | $debug, |
|||||
85 | 12 | $checkEvents, |
|||||
86 | 12 | $environment, |
|||||
87 | 12 | $bootstrapGroup, |
|||||
88 | 12 | $eventsGroup, |
|||||
89 | 12 | $diGroup, |
|||||
90 | 12 | $diProvidersGroup, |
|||||
91 | 12 | $diDelegatesGroup, |
|||||
92 | 12 | $diTagsGroup, |
|||||
93 | 12 | $paramsGroup, |
|||||
94 | 12 | $nestedParamsGroups, |
|||||
95 | 12 | $nestedEventsGroups, |
|||||
96 | 12 | ); |
|||||
97 | } |
||||||
98 | |||||||
99 | /** |
||||||
100 | * Returns a new instance with the specified temporary error handler instance {@see ErrorHandler}. |
||||||
101 | * |
||||||
102 | * A temporary error handler is needed to handle the creation of configuration and container instances, |
||||||
103 | * then the error handler configured in your application configuration will be used. |
||||||
104 | * |
||||||
105 | * @param ErrorHandler $temporaryErrorHandler The temporary error handler instance. |
||||||
106 | */ |
||||||
107 | 2 | public function withTemporaryErrorHandler(ErrorHandler $temporaryErrorHandler): self |
|||||
108 | { |
||||||
109 | 2 | $new = clone $this; |
|||||
110 | 2 | $new->temporaryErrorHandler = $temporaryErrorHandler; |
|||||
111 | 2 | return $new; |
|||||
112 | } |
||||||
113 | |||||||
114 | /** |
||||||
115 | * Returns a new instance with the specified PSR-7 worker instance {@see PSR7WorkerInterface}. |
||||||
116 | * |
||||||
117 | * @param PSR7WorkerInterface $worker The PSR-7 worker instance. |
||||||
118 | */ |
||||||
119 | 12 | public function withPsr7Worker(PSR7WorkerInterface $worker): self |
|||||
120 | { |
||||||
121 | 12 | $new = clone $this; |
|||||
122 | 12 | $new->psr7Worker = $worker; |
|||||
123 | 12 | return $new; |
|||||
124 | } |
||||||
125 | |||||||
126 | /** |
||||||
127 | * Returns a new instance with enabled temporal support. |
||||||
128 | */ |
||||||
129 | 2 | public function withTemporalEnabled(bool $value): self |
|||||
130 | { |
||||||
131 | 2 | if (!$this->isTemporalSDKInstalled()) { |
|||||
132 | throw new Exception('Temporal SDK is not installed. To install the SDK run `composer require temporal/sdk`.'); |
||||||
133 | } |
||||||
134 | 2 | $new = clone $this; |
|||||
135 | 2 | $new->isTemporalEnabled = $value; |
|||||
136 | 2 | return $new; |
|||||
137 | } |
||||||
138 | |||||||
139 | /** |
||||||
140 | * {@inheritDoc} |
||||||
141 | * |
||||||
142 | * @throws CircularReferenceException|ErrorException|InvalidConfigException|JsonException |
||||||
143 | * @throws ContainerExceptionInterface|NotFoundException|NotFoundExceptionInterface|NotInstantiableException |
||||||
144 | */ |
||||||
145 | 11 | public function run(): void |
|||||
146 | { |
||||||
147 | // Register temporary error handler to catch error while container is building. |
||||||
148 | 11 | $temporaryErrorHandler = $this->createTemporaryErrorHandler(); |
|||||
149 | 11 | $this->registerErrorHandler($temporaryErrorHandler); |
|||||
150 | |||||||
151 | 11 | $container = $this->getContainer(); |
|||||
152 | |||||||
153 | // Register error handler with real container-configured dependencies. |
||||||
154 | /** @var ErrorHandler $actualErrorHandler */ |
||||||
155 | 11 | $actualErrorHandler = $container->get(ErrorHandler::class); |
|||||
156 | 11 | $this->registerErrorHandler($actualErrorHandler, $temporaryErrorHandler); |
|||||
157 | |||||||
158 | 11 | $this->runBootstrap(); |
|||||
159 | 11 | $this->checkEvents(); |
|||||
160 | |||||||
161 | 10 | $env = Environment::fromGlobals(); |
|||||
162 | |||||||
163 | 10 | if ($env->getMode() === Mode::MODE_TEMPORAL) { |
|||||
164 | 2 | if (!$this->isTemporalEnabled) { |
|||||
165 | 1 | throw new RuntimeException( |
|||||
166 | 1 | 'Temporal support is disabled. You should call `withTemporalEnabled(true)` to enable temporal support.', |
|||||
167 | 1 | ); |
|||||
168 | } |
||||||
169 | 1 | $this->runTemporal($container); |
|||||
170 | 1 | return; |
|||||
171 | } |
||||||
172 | 8 | if ($env->getMode() === Mode::MODE_HTTP) { |
|||||
173 | 7 | $this->runRoadRunner($container); |
|||||
174 | 7 | return; |
|||||
175 | } |
||||||
176 | |||||||
177 | 1 | throw new RuntimeException(sprintf( |
|||||
178 | 1 | 'Unsupported mode "%s", supported modes are: "%s".', |
|||||
179 | 1 | $env->getMode(), |
|||||
180 | 1 | implode('", "', [Mode::MODE_HTTP, Mode::MODE_TEMPORAL]), |
|||||
181 | 1 | )); |
|||||
182 | } |
||||||
183 | |||||||
184 | 11 | private function createTemporaryErrorHandler(): ErrorHandler |
|||||
185 | { |
||||||
186 | 11 | if ($this->temporaryErrorHandler !== null) { |
|||||
187 | 1 | return $this->temporaryErrorHandler; |
|||||
188 | } |
||||||
189 | |||||||
190 | 10 | $logger = new Logger([new FileTarget("$this->rootPath/runtime/logs/app.log")]); |
|||||
191 | 10 | return new ErrorHandler($logger, new HtmlRenderer()); |
|||||
192 | } |
||||||
193 | |||||||
194 | /** |
||||||
195 | * @throws ErrorException |
||||||
196 | */ |
||||||
197 | 11 | private function registerErrorHandler(ErrorHandler $registered, ErrorHandler $unregistered = null): void |
|||||
198 | { |
||||||
199 | 11 | $unregistered?->unregister(); |
|||||
200 | |||||||
201 | 11 | if ($this->debug) { |
|||||
202 | 11 | $registered->debug(); |
|||||
203 | } |
||||||
204 | |||||||
205 | 11 | $registered->register(); |
|||||
206 | } |
||||||
207 | |||||||
208 | 6 | private function afterRespond( |
|||||
209 | Application $application, |
||||||
210 | ContainerInterface $container, |
||||||
211 | ?ResponseInterface $response, |
||||||
212 | ): void { |
||||||
213 | 6 | $application->afterEmit($response); |
|||||
214 | /** @psalm-suppress MixedMethodCall */ |
||||||
215 | 6 | $container |
|||||
216 | 6 | ->get(StateResetter::class) |
|||||
217 | 6 | ->reset(); // We should reset the state of such services every request. |
|||||
218 | 6 | gc_collect_cycles(); |
|||||
219 | } |
||||||
220 | |||||||
221 | 7 | private function runRoadRunner(ContainerInterface $container): void |
|||||
222 | { |
||||||
223 | 7 | $worker = new RoadRunnerHttpWorker($container, $this->psr7Worker); |
|||||
224 | |||||||
225 | /** @var Application $application */ |
||||||
226 | 7 | $application = $container->get(Application::class); |
|||||
227 | 7 | $application->start(); |
|||||
228 | |||||||
229 | 7 | while (true) { |
|||||
230 | 7 | $request = $worker->waitRequest(); |
|||||
231 | 7 | $response = null; |
|||||
232 | |||||||
233 | 7 | if ($request === null) { |
|||||
234 | 7 | break; |
|||||
235 | } |
||||||
236 | |||||||
237 | 6 | if ($request instanceof Throwable) { |
|||||
238 | 1 | $response = $worker->respondWithError($request); |
|||||
239 | 1 | $this->afterRespond($application, $container, $response); |
|||||
240 | 1 | continue; |
|||||
241 | } |
||||||
242 | |||||||
243 | try { |
||||||
244 | 5 | $response = $application->handle($request); |
|||||
245 | 4 | $worker->respond($response); |
|||||
246 | 1 | } catch (Throwable $t) { |
|||||
247 | 1 | $response = $worker->respondWithError($t, $request); |
|||||
248 | } finally { |
||||||
249 | 5 | $this->afterRespond($application, $container, $response); |
|||||
250 | } |
||||||
251 | } |
||||||
252 | |||||||
253 | 7 | $application->shutdown(); |
|||||
254 | } |
||||||
255 | |||||||
256 | 1 | private function runTemporal(ContainerInterface $container): void |
|||||
257 | { |
||||||
258 | /** @var TemporalDeclarationProvider $temporalDeclarationProvider */ |
||||||
259 | 1 | $temporalDeclarationProvider = $container->get(TemporalDeclarationProvider::class); |
|||||
260 | /** @var HostConnectionInterface $host */ |
||||||
261 | 1 | $host = $container->get(HostConnectionInterface::class); |
|||||
262 | |||||||
263 | /** @var WorkerFactoryInterface $factory */ |
||||||
264 | 1 | $factory = $container->get(WorkerFactoryInterface::class); |
|||||
265 | 1 | $worker = $factory->newWorker('default'); |
|||||
266 | |||||||
267 | 1 | $workflows = $temporalDeclarationProvider->getWorkflows(); |
|||||
268 | 1 | $activities = $temporalDeclarationProvider->getActivities(); |
|||||
269 | |||||||
270 | 1 | $worker->registerWorkflowTypes(...$workflows); |
|||||
271 | |||||||
272 | /** @psalm-suppress MixedReturnStatement,MixedInferredReturnType */ |
||||||
273 | 1 | $activityFactory = static fn (ReflectionClass $class): object => $container->get($class->getName()); |
|||||
274 | 1 | $activityFinalizer = static function () use ($container): void { |
|||||
275 | /** @psalm-suppress MixedMethodCall */ |
||||||
276 | $container |
||||||
277 | ->get(StateResetter::class) |
||||||
278 | ->reset(); // We should reset the state of such services every request. |
||||||
279 | gc_collect_cycles(); |
||||||
280 | 1 | }; |
|||||
281 | |||||||
282 | 1 | foreach ($activities as $activity) { |
|||||
283 | $worker->registerActivity($activity, $activityFactory); |
||||||
284 | } |
||||||
285 | 1 | $worker->registerActivityFinalizer($activityFinalizer); |
|||||
286 | |||||||
287 | /** @psalm-suppress TooManyArguments */ |
||||||
288 | 1 | $factory->run($host); |
|||||
0 ignored issues
–
show
The call to
Temporal\Worker\WorkerFactoryInterface::run() has too many arguments starting with $host .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
289 | } |
||||||
290 | |||||||
291 | 2 | private function isTemporalSDKInstalled(): bool |
|||||
292 | { |
||||||
293 | 2 | return interface_exists(WorkerFactoryInterface::class); |
|||||
294 | } |
||||||
295 | } |
||||||
296 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths