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