DISorcery::initialize()   A
last analyzed

Complexity

Conditions 6
Paths 32

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 18
c 1
b 0
f 0
nc 32
nop 1
dl 0
loc 34
ccs 18
cts 18
cp 1
crap 6
rs 9.0444
1
<?php
2
3
namespace Faxity\DI;
4
5
use Anax\DI\DIMagicTrait;
6
use Anax\DI\DIFactoryConfig;
7
use Anax\DI\Exception\Exception;
8
use Psr\Container\ContainerInterface;
9
10
/**
11
 * Extending DI factory class with magic methods for getters to allow
12
 * easy usage to $di as $di->service, compare to a $app.
13
 *
14
 * This version also includes autoloading of configs, views and DI services.
15
 */
16
class DISorcery extends DIFactoryConfig implements ContainerInterface
17
{
18
    use DIMagicTrait;
19
20
    /** Regex to check if a path is absolute */
21
    const PATH_REGEX = '~\A[A-Z]:(?![^/\\\\])~i';
22
23
    /**
24
     * @var array  $sources     List of sources to load from
25
     * @var bool   $initialized If services has been loaded before
26
     * @var string $appRoot     Anax app root directory
27
     * @var string $sourcesRoot Directory to load relative source paths
28
     */
29
    private $sources = [];
30
    private $initialized = false;
31
    private $appRoot;
32
    private $sourcesRoot;
33
34
35
    /**
36
     * Resolves a relative path to a custom root, if not absolute
37
     * @param string $root Root path
38
     * @param string $path Relative path to resolve
39
     *
40
     * @return string
41
     */
42 2
    protected function resolvePath(string $root, string $path)
43
    {
44 2
        return $path[0] !== DIRECTORY_SEPARATOR && preg_match(self::PATH_REGEX, $path) == 0
45 2
            ? "$root/$path"
46 2
            : $path;
47
    }
48
49
50
    /**
51
     * Custom loader for 'configuration' service
52
     *
53
     * @return callable
54
     */
55 1
    protected function configLoader(): callable
56
    {
57
        return function () {
58 1
            $config = new \Anax\Configure\Configuration();
59
            $dirs = array_reduce($this->sources, function ($paths, $path) {
60 1
                if (is_dir("$path/config")) {
61 1
                    $paths[] = "$path/config";
62
                }
63
64 1
                return $paths;
65 1
            }, []);
66
67 1
            $config->setBaseDirectories($dirs);
68 1
            return $config;
69 1
        };
70
    }
71
72
73
    /**
74
     * Custom loader for 'view' service.
75
     *
76
     * @return callable
77
     */
78 1
    protected function viewLoader(): callable
79
    {
80 1
        $loader = $this->loaded['view']['loader'];
81
82
        return function () use ($loader) {
83 1
            $view = $loader();
84
            $dirs = array_reduce($this->sources, function ($paths, $path) {
85 1
                if (is_dir("$path/view")) {
86 1
                    $paths[] = "$path/view";
87
                }
88
89 1
                return $paths;
90 1
            }, []);
91
92 1
            $view->setPaths($dirs);
93 1
            return $view;
94 1
        };
95
    }
96
97
98
    /**
99
     * Create a service from a name and an array containing details on
100
     * how to create it.
101
     * @param string $name    of service.
102
     * @param array  $service details to use when creating the service.
103
     *
104
     * @throws \Anax\DI\Exception\Exception when configuration is corrupt.
105
     * @return void
106
     */
107 5
    protected function createService(string $name, array $service): void
108
    {
109 5
        if (!isset($service["callback"])) {
110 1
            throw new Exception("The service '$name' is missing a callback.");
111
        }
112
113 4
        if (isset($service["shared"]) && $service["shared"]) {
114 4
            $this->setShared($name, $service["callback"]);
115
        } else {
116 3
            $this->set($name, $service["callback"]);
117
        }
118
119 4
        if (isset($service["active"]) && $service["active"]) {
120 3
            if ($this->initialized) {
121 1
                $this->get($name);
122
            } else {
123 2
                $this->active[$name] = null; // Set to null to show its not loaded yet
124
            }
125
        }
126 4
    }
127
128
129
    /**
130
     * Constructs class instance, loads sources from file if available.
131
     * @param string      $appRoot     Anax app root directory
132
     * @param string|null $sourcesRoot (optional) Directory to load relative source paths, defaults to "$appRoot/vendor"
133
     *
134
     * @return DISorcery
135
     */
136 6
    public function __construct(string $appRoot, ?string $sourcesRoot = null)
137
    {
138 6
        $this->appRoot = $appRoot;
139 6
        $this->sourcesRoot = $sourcesRoot ?? "$appRoot/vendor";
140 6
    }
141
142
143
    /**
144
     * Gets the source folders to load views, config and DI services from.
145
     *
146
     * @return array
147
     */
148 2
    public function getSources(): array
149
    {
150 2
        return $this->sources;
151
    }
152
153
154
    /**
155
     * Create services by using $item as a reference to find a
156
     * configuration for the services. The $item can be an array,
157
     * a file.php, or an directory containing files named *.php.
158
     *
159
     * @param array|string $item referencing the source for configuration.
160
     *
161
     * @return $this
162
     */
163 6
    public function loadServices($item) : object
164
    {
165 6
        if (is_array($item)) {
166 1
            $this->createServicesFromArray($item, "array");
167 6
        } else if (is_readable($item) && is_file($item)) {
168 1
            $services = require $item;
169 1
            $this->createServicesFromArray($services, $item);
170
        } else {
171 6
            if (is_readable("$item.php") && is_file("$item.php")) {
172 1
                $services = require "$item.php";
173 1
                $this->createServicesFromArray($services, $item);
174
            }
175
176 6
            if (is_readable($item) && is_dir($item)) {
177 4
                foreach (glob("$item/*.php") as $file) {
178 4
                    $services = require "$file";
179 4
                    $this->createServicesFromArray($services, $file);
180
                }
181
            }
182
        }
183
184 5
        return $this;
185
    }
186
187
188
    /**
189
     * Loads sources and initializes the services.
190
     * @param string|null $sourcesFile (optional) File to load other Anax source directories from.
191
     *
192
     * @return void
193
     */
194 6
    public function initialize(?string $sourcesFile = null): void
195
    {
196 6
        $this->initialized = false;
197 6
        $sources = [ $this->appRoot ];
198
199 6
        if (is_string($sourcesFile)) {
200 2
            $sourcesFile = $this->resolvePath($this->appRoot, $sourcesFile);
201 2
            $dirs = require $sourcesFile;
202
203
            $sources = array_map(function ($path) {
204 2
                return $this->resolvePath($this->sourcesRoot, $path);
205 2
            }, array_merge($sources, $dirs));
206
        }
207
208
        // Reverse the order so we overwrite the later services.
209 6
        foreach (array_reverse($sources) as $source) {
210 6
            $this->loadServices("$source/config/di");
211
        }
212
213
        // Path loaders in services that needs access to sources
214 5
        if (array_key_exists('configuration', $this->loaded)) {
215 1
            $this->loaded['configuration']['loader'] = $this->configLoader();
216
        }
217
218 5
        if (array_key_exists('view', $this->loaded)) {
219 1
            $this->loaded['view']['loader'] = $this->viewLoader();
220
        }
221
222 5
        $this->initialized = true;
223 5
        $this->sources = $sources;
224
225
        // Preload active services
226 5
        foreach (array_keys($this->active) as $name) {
227 2
            $this->get($name);
228
        }
229 5
    }
230
}
231