Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.97% covered (success)
95.97%
119 / 124
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApieServiceProvider
95.97% covered (success)
95.97%
119 / 124
50.00% covered (danger)
50.00%
2 / 4
26
0.00% covered (danger)
0.00%
0 / 1
 autoTagHashmapActions
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
4.00
 boot
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
 register
94.03% covered (success)
94.03%
63 / 67
0.00% covered (danger)
0.00%
0 / 1
14.04
 sanitizeConfig
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2namespace Apie\LaravelApie;
3
4use Apie\ApieCommonPlugin\ApieCommonPluginServiceProvider;
5use Apie\CmsApiDropdownOption\CmsDropdownServiceProvider;
6use Apie\Common\CommonServiceProvider;
7use Apie\Common\ContextBuilders\FrameworkContextBuilder;
8use Apie\Common\Interfaces\BoundedContextSelection;
9use Apie\Common\Interfaces\DashboardContentFactoryInterface;
10use Apie\Common\Wrappers\BoundedContextHashmapFactory;
11use Apie\Common\Wrappers\ConsoleCommandFactory as CommonConsoleCommandFactory;
12use Apie\Console\ConsoleServiceProvider;
13use Apie\Core\CoreServiceProvider;
14use Apie\Core\Session\CsrfTokenProvider;
15use Apie\DoctrineEntityConverter\DoctrineEntityConverterProvider;
16use Apie\DoctrineEntityDatalayer\Commands\ApieUpdateIdfCommand;
17use Apie\DoctrineEntityDatalayer\DoctrineEntityDatalayerServiceProvider;
18use Apie\DoctrineEntityDatalayer\EntityReindexer;
19use Apie\DoctrineEntityDatalayer\IndexStrategy\BackgroundIndexStrategy;
20use Apie\DoctrineEntityDatalayer\IndexStrategy\DirectIndexStrategy;
21use Apie\DoctrineEntityDatalayer\IndexStrategy\IndexAfterResponseIsSentStrategy;
22use Apie\DoctrineEntityDatalayer\IndexStrategy\IndexStrategyInterface;
23use Apie\Faker\FakerServiceProvider;
24use Apie\HtmlBuilders\ErrorHandler\CmsErrorRenderer;
25use Apie\HtmlBuilders\HtmlBuilderServiceProvider;
26use Apie\LaravelApie\Config\LaravelConfiguration;
27use Apie\LaravelApie\ContextBuilders\CsrfTokenContextBuilder;
28use Apie\LaravelApie\ContextBuilders\RegisterBoundedContextActionContextBuilder;
29use Apie\LaravelApie\ContextBuilders\SessionContextBuilder;
30use Apie\LaravelApie\ErrorHandler\ApieErrorRenderer;
31use Apie\LaravelApie\ErrorHandler\Handler;
32use Apie\LaravelApie\Providers\CmsServiceProvider;
33use Apie\LaravelApie\Providers\SecurityServiceProvider;
34use Apie\LaravelApie\Wrappers\Cms\DashboardContentFactory;
35use Apie\LaravelApie\Wrappers\Core\BoundedContextSelected;
36use Apie\LaravelApie\Wrappers\Queue\BackgroundProcessPersistListener;
37use Apie\Maker\MakerServiceProvider;
38use Apie\RestApi\RestApiServiceProvider;
39use Apie\SchemaGenerator\SchemaGeneratorServiceProvider;
40use Apie\Serializer\SerializerServiceProvider;
41use Apie\ServiceProviderGenerator\TagMap;
42use Illuminate\Config\Repository;
43use Illuminate\Contracts\Debug\ExceptionHandler;
44use Illuminate\Contracts\Events\Dispatcher;
45use Illuminate\Support\ServiceProvider;
46use Psr\EventDispatcher\EventDispatcherInterface;
47use Psr\Http\Message\ServerRequestInterface;
48use Symfony\Component\Config\ConfigCache;
49use Symfony\Component\Config\Definition\Processor;
50use Symfony\Component\Config\Resource\ReflectionClassResource;
51use Symfony\Component\Console\Application;
52use Symfony\Component\EventDispatcher\EventDispatcher;
53
54class ApieServiceProvider extends ServiceProvider
55{
56    /**
57     * @var array<string, array<int, class-string<ServiceProvider>>> $dependencies
58     */
59    private array $dependencies = [
60        'enable_common_plugin' => [
61            ApieCommonPluginServiceProvider::class,
62        ],
63        'enable_cms' => [
64            CommonServiceProvider::class,
65            HtmlBuilderServiceProvider::class, // it's important that this loads before CmsServiceProvider!!!
66            CmsServiceProvider::class,
67            SerializerServiceProvider::class,
68        ],
69        'enable_cms_dropdown' => [
70            CommonServiceProvider::class,
71            CmsDropdownServiceProvider::class,
72        ],
73        'enable_core' => [
74            CoreServiceProvider::class,
75        ],
76        'enable_console' => [
77            CommonServiceProvider::class,
78            ConsoleServiceProvider::class,
79            SerializerServiceProvider::class,
80        ],
81        'enable_doctrine_entity_converter' => [
82            CoreServiceProvider::class,
83            DoctrineEntityConverterProvider::class,
84        ],
85        'enable_doctrine_entity_datalayer' => [
86            CoreServiceProvider::class,
87            DoctrineEntityConverterProvider::class,
88            DoctrineEntityDatalayerServiceProvider::class,
89        ],
90        'enable_security' => [
91            CommonServiceProvider::class,
92            SerializerServiceProvider::class,
93            SecurityServiceProvider::class,
94        ],
95        'enable_rest_api' => [
96            CommonServiceProvider::class,
97            RestApiServiceProvider::class,
98            SchemaGeneratorServiceProvider::class,
99            SerializerServiceProvider::class,
100        ],
101        'enable_faker' => [
102            FakerServiceProvider::class,
103        ],
104        'enable_maker' => [
105            MakerServiceProvider::class,
106        ],
107    ];
108
109    private function autoTagHashmapActions(): void
110    {
111        $boundedContextConfig = config('apie.bounded_contexts');
112        $scanBoundedContextConfig = config('apie.scan_bounded_contexts');
113        $factory = new BoundedContextHashmapFactory(
114            $boundedContextConfig ?? [],
115            $scanBoundedContextConfig ?? [],
116            new EventDispatcher(),
117        );
118        $hashmap = $factory->create();
119        foreach ($hashmap as $boundedContext) {
120            foreach ($boundedContext->actions as $action) {
121                $class = $action->getDeclaringClass();
122                if (!$class->isInstantiable()) {
123                    continue;
124                }
125                $className = $class->name;
126                TagMap::register(
127                    $this->app,
128                    $className,
129                    ['apie.context']
130                );
131            }
132        }
133    }
134
135    public function boot(): void
136    {
137        $this->autoTagHashmapActions();
138        $this->loadViewsFrom(__DIR__ . '/../templates', 'apie');
139        $this->loadRoutesFrom(__DIR__.'/../resources/routes.php');
140        TagMap::registerEvents($this->app);
141
142        if ($this->app->runningInConsole()) {
143            $commands = [];
144            $commands[] = ApieUpdateIdfCommand::class;
145            // for some reason these are not called in integration tests without re-registering them
146            foreach (TagMap::getServiceIdsWithTag($this->app, 'console.command') as $taggedCommand) {
147                $serviceId = 'apie.console.tagged.' . $taggedCommand;
148                $this->app->singleton($serviceId, function () use ($taggedCommand) {
149                    return $this->app->get($taggedCommand);
150                });
151                $commands[] = $serviceId;
152            }
153            /** @var CommonConsoleCommandFactory $factory */
154            $factory = $this->app->get('apie.console.factory');
155            foreach ($factory->create($this->app->get(Application::class)) as $command) {
156                $serviceId = 'apie.console.registered.' . $command->getName();
157                $this->app->instance($serviceId, $command);
158                $commands[] = $serviceId;
159            }
160            $this->commands($commands);
161        }
162    }
163
164    public function register()
165    {
166        $this->mergeConfigFrom(__DIR__ . '/../resources/apie.php', 'apie');
167
168        $this->app->bind(FrameworkContextBuilder::class, function () {
169            return new FrameworkContextBuilder('laravel');
170        });
171        TagMap::register($this->app, FrameworkContextBuilder::class, ['apie.core.context_builder']);
172
173        // add PSR-14 support if needed:
174        if (!$this->app->bound(EventDispatcherInterface::class)) {
175            $this->app->bind(EventDispatcherInterface::class, function () {
176                return new class($this->app->make(Dispatcher::class)) implements EventDispatcherInterface {
177                    public function __construct(private readonly Dispatcher $dispatcher)
178                    {
179                    }
180
181                    public function dispatch(object $event): object
182                    {
183                        $this->dispatcher->dispatch($event);
184                        return $event;
185                    }
186                };
187            });
188        }
189
190        // fix for https://github.com/laravel/framework/issues/30415
191        $this->app->extend(
192            ServerRequestInterface::class,
193            function (ServerRequestInterface $psrRequest) {
194                $route = $this->app->make('request')->route();
195                if ($route) {
196                    $parameters = $route->parameters();
197                    foreach ($parameters as $key => $value) {
198                        $psrRequest = $psrRequest->withAttribute($key, $value);
199                    }
200                }
201                return $psrRequest;
202            }
203        );
204
205        $this->app->bind(IndexStrategyInterface::class, function () {
206            $config = config();
207            if ($config->get('apie.enable_doctrine_entity_datalayer')) {
208                $type = $config->get('apie.doctrine.indexing.type', 'direct');
209                return match ($type) {
210                    'direct' => new DirectIndexStrategy($this->app->get(EntityReindexer::class)),
211                    'late' => new IndexAfterResponseIsSentStrategy($this->app->get(EntityReindexer::class)),
212                    'background' => new BackgroundIndexStrategy(),
213                    default => $this->app->get(config('apie.doctrine.indexing.service', DirectIndexStrategy::class)),
214                };
215            }
216
217            return new DirectIndexStrategy($this->app->get(EntityReindexer::class));
218        });
219
220        $this->app->bind(ApieErrorRenderer::class, function () {
221            return new ApieErrorRenderer(
222                $this->app->bound(CmsErrorRenderer::class) ? $this->app->make(CmsErrorRenderer::class) : null,
223                $this->app->make(\Apie\Common\ErrorHandler\ApiErrorRenderer::class),
224                config('apie.cms.base_url')
225            );
226        });
227
228        $this->app->extend(ExceptionHandler::class, function (ExceptionHandler $service) {
229            return new Handler($this->app, $service);
230        });
231        
232        $this->app->bind(DashboardContentFactoryInterface::class, DashboardContentFactory::class);
233        $this->app->bind(BoundedContextSelection::class, BoundedContextSelected::class);
234
235        $alreadyRegistered = [];
236        foreach ($this->dependencies as $configKey => $dependencies) {
237            if (config('apie.' . $configKey, false)) {
238                foreach ($dependencies as $dependency) {
239                    if (!isset($alreadyRegistered[$dependency])) {
240                        $alreadyRegistered[$dependency] = $dependency;
241                        $this->app->register($dependency);
242                    }
243                }
244            }
245        }
246        //$this->app->bind(CsrfTokenProvider::class, CsrfTokenContextBuilder::class);
247        TagMap::register($this->app, CsrfTokenContextBuilder::class, ['apie.core.context_builder']);
248        $this->app->tag(CsrfTokenContextBuilder::class, ['apie.core.context_builder']);
249
250        // this has to be added after CsrfTokenContextBuilder!
251        $this->app->bind(SessionContextBuilder::class);
252        TagMap::register($this->app, SessionContextBuilder::class, ['apie.core.context_builder']);
253        $this->app->tag(SessionContextBuilder::class, ['apie.core.context_builder']);
254
255        TagMap::register($this->app, RegisterBoundedContextActionContextBuilder::class, ['apie.core.context_builder']);
256        $this->app->tag(RegisterBoundedContextActionContextBuilder::class, ['apie.core.context_builder']);
257        $this->app->extend('config', function (Repository $config) {
258            $this->sanitizeConfig($config);
259            return $config;
260        });
261
262        TagMap::register($this->app, BackgroundProcessPersistListener::class, ['kernel.event_subscriber']);
263    }
264
265    private function sanitizeConfig(Repository $config): void
266    {
267        $rawConfig = $config->get('apie');
268        $path = storage_path('framework/cache/apie-config' . md5(json_encode($rawConfig)) . '.php');
269        $resources = [
270            new ReflectionClassResource(new \ReflectionClass(LaravelConfiguration::class)),
271            new ReflectionClassResource(new \ReflectionClass(static::class)),
272        ];
273        $configCache = new ConfigCache($path, true);
274        if ($configCache->isFresh()) {
275            $processedConfig = require $path;
276        } else {
277            $configuration = new LaravelConfiguration();
278
279            $processor = new Processor();
280
281            $processedConfig = $processor->processConfiguration($configuration, ['apie' => $rawConfig]);
282
283            if (!isset($processedConfig['scan_bounded_contexts'])) {
284                $processedConfig['scan_bounded_contexts'] = [];
285            }
286            if (empty($processedConfig['storage'])) {
287                $processedConfig['storage'] = null;
288            }
289            $code = '<?php' . PHP_EOL . 'return ' . var_export($processedConfig, true) . ';';
290            $configCache->write($code, $resources);
291        }
292
293        $config->set('apie', $processedConfig);
294    }
295}