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