Gacela::rootDir()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework;
6
7
use Closure;
8
use Gacela\Framework\Bootstrap\GacelaConfig;
9
use Gacela\Framework\Bootstrap\SetupGacela;
10
use Gacela\Framework\Bootstrap\SetupGacelaInterface;
11
use Gacela\Framework\ClassResolver\AbstractClassResolver;
12
use Gacela\Framework\ClassResolver\Cache\GacelaFileCache;
13
use Gacela\Framework\ClassResolver\Cache\InMemoryCache;
14
use Gacela\Framework\ClassResolver\ClassResolverCache;
15
use Gacela\Framework\ClassResolver\GlobalInstance\AnonymousGlobal;
16
use Gacela\Framework\Config\Config;
17
use Gacela\Framework\Config\ConfigFactory;
18
use Gacela\Framework\Container\Container;
19
use Gacela\Framework\Container\Locator;
20
use Gacela\Framework\Exception\GacelaNotBootstrappedException;
21
use Gacela\Framework\Exception\ServiceNotFoundException;
22
use Gacela\Framework\ServiceResolver\DocBlockResolverCache;
23
24
use function is_string;
25
use function sprintf;
26
27
final class Gacela
28
{
29
    private const GACELA_PHP_FILENAME = 'gacela.php';
30
31
    private static ?Container $mainContainer = null;
32
33
    private static ?string $appRootDir = null;
34
35
    /**
36
     * Define the entry point of Gacela.
37
     *
38
     * @param  null|Closure(GacelaConfig):void  $configFn
39
     */
40
    public static function bootstrap(string $appRootDir, ?Closure $configFn = null): void
41
    {
42
        self::$appRootDir = $appRootDir;
43
        self::$mainContainer = null;
44
45
        $setup = self::processConfigFnIntoSetup($configFn);
46
47
        if ($setup->shouldResetInMemoryCache()) {
48
            self::resetCache();
49
        }
50
51
        $config = Config::createWithSetup($setup);
52
        $config->setAppRootDir($appRootDir)
53
            ->init();
54
55
        self::runPlugins($config);
56
    }
57
58
    /**
59
     * @template T
60
     *
61
     * @param  class-string<T>  $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
62
     *
63
     * @return T|null
64
     */
65
    public static function get(string $className): mixed
66
    {
67
        return Locator::getSingleton($className, self::$mainContainer);
68
    }
69
70
    /**
71
     * Get a service from the container, throwing an exception if not found.
72
     * Use this when you expect the service to exist and want type-safe returns.
73
     *
74
     * @template T of object
75
     *
76
     * @param  class-string<T>  $className
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<T> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<T>.
Loading history...
77
     *
78
     * @throws ServiceNotFoundException
79
     *
80
     * @return T
81
     */
82
    public static function getRequired(string $className): object
83
    {
84
        return Locator::getRequiredSingleton($className, self::$mainContainer);
85
    }
86
87
    /**
88
     * Get the main dependency injection container.
89
     * This is the actual container created during bootstrap with all runtime bindings and frozen services.
90
     *
91
     * @throws GacelaNotBootstrappedException if Gacela has not been bootstrapped yet
92
     */
93
    public static function container(): Container
94
    {
95
        if (!self::$mainContainer instanceof Container) {
96
            throw new GacelaNotBootstrappedException();
97
        }
98
99
        return self::$mainContainer;
100
    }
101
102
    /**
103
     * Get the application root dir set when bootstrapping gacela
104
     */
105
    public static function rootDir(): string
106
    {
107
        if (self::$appRootDir === null) {
108
            throw new GacelaNotBootstrappedException();
109
        }
110
111
        return self::$appRootDir;
112
    }
113
114
    /**
115
     * Add an anonymous class as 'Config', 'Factory' or 'Provider' as a global resource
116
     * bound to the context that it is passed as second argument.
117
     *
118
     * @param  object|string  $context  It can be the string-key (file path) or the class/object itself.
119
     *                               If empty then the caller's file will be use
120
     */
121
    public static function addGlobal(object $resolvedClass, object|string $context = ''): void
122
    {
123
        if (is_string($context) && is_file($context)) {
124
            $context = basename($context, '.php');
125
        } elseif ($context === '') {
126
            $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
127
            $callerFile = $trace[0]['file'] ?? __FILE__;
128
            $context = basename($callerFile, '.php');
129
        }
130
131
        AnonymousGlobal::addGlobal($context, $resolvedClass);
132
    }
133
134
    public static function overrideExistingResolvedClass(string $className, object $resolvedClass): void
135
    {
136
        AnonymousGlobal::overrideExistingResolvedClass($className, $resolvedClass);
137
    }
138
139
    /**
140
     * @param  null|Closure(GacelaConfig):void  $configFn
141
     */
142
    private static function processConfigFnIntoSetup(?Closure $configFn = null): SetupGacelaInterface
143
    {
144
        if ($configFn instanceof Closure) {
145
            return SetupGacela::fromCallable($configFn);
146
        }
147
148
        $gacelaFilePath = sprintf(
149
            '%s%s%s',
150
            self::rootDir(),
151
            DIRECTORY_SEPARATOR,
152
            self::GACELA_PHP_FILENAME,
153
        );
154
155
        if (is_file($gacelaFilePath)) {
156
            return SetupGacela::fromFile($gacelaFilePath);
157
        }
158
159
        return new SetupGacela();
160
    }
161
162
    private static function resetCache(): void
163
    {
164
        AnonymousGlobal::resetCache();
165
        AbstractFacade::resetCache();
166
        AbstractFactory::resetCache();
167
        AbstractClassResolver::resetCache();
168
        InMemoryCache::resetCache();
169
        GacelaFileCache::resetCache();
170
        DocBlockResolverCache::resetCache();
171
        ClassResolverCache::resetCache();
172
        ConfigFactory::resetCache();
173
        Config::resetInstance();
174
        Locator::resetInstance();
175
    }
176
177
    private static function runPlugins(Config $config): void
178
    {
179
        self::$mainContainer = Container::withConfig($config);
180
181
        $plugins = $config->getSetupGacela()->getPlugins();
182
183
        foreach ($plugins as $plugin) {
184
            /** @var callable $current */
185
            $current = is_string($plugin)
186
                ? self::$mainContainer->get($plugin)
187
                : $plugin;
188
189
            self::$mainContainer->resolve($current);
190
        }
191
    }
192
}
193