Completed
Push — master ( b6d688...cba8ad )
by Alexander
11s
created

AspectKernel::init()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 34
c 0
b 0
f 0
ccs 0
cts 19
cp 0
rs 8.5806
cc 4
eloc 18
nc 5
nop 1
crap 20
1
<?php
2
declare(strict_types = 1);
3
/*
4
 * Go! AOP framework
5
 *
6
 * @copyright Copyright 2012, Lisachenko Alexander <[email protected]>
7
 *
8
 * This source file is subject to the license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Go\Core;
13
14
use Go\Aop\Features;
15
use Go\Instrument\ClassLoading\AopComposerLoader;
16
use Go\Instrument\ClassLoading\SourceTransformingLoader;
17
use Go\Instrument\PathResolver;
18
use Go\Instrument\Transformer\ConstructorExecutionTransformer;
19
use Go\Instrument\Transformer\SourceTransformer;
20
use Go\Instrument\Transformer\WeavingTransformer;
21
use Go\Instrument\Transformer\CachingTransformer;
22
use Go\Instrument\Transformer\FilterInjectorTransformer;
23
use Go\Instrument\Transformer\MagicConstantTransformer;
24
25
/**
26
 * Abstract aspect kernel is used to prepare an application to work with aspects.
27
 */
28
abstract class AspectKernel
29
{
30
31
    /**
32
     * Version of kernel
33
     */
34
    const VERSION = '2.1.0';
35
36
    /**
37
     * Kernel options
38
     *
39
     * @var array
40
     */
41
    protected $options = [
42
        'features' => 0
43
    ];
44
45
    /**
46
     * Single instance of kernel
47
     *
48
     * @var static
49
     */
50
    protected static $instance;
51
52
    /**
53
     * Default class name for container, can be redefined in children
54
     *
55
     * @var string
56
     */
57
    protected static $containerClass = GoAspectContainer::class;
58
59
    /**
60
     * Flag to determine if kernel was already initialized or not
61
     *
62
     * @var bool
63
     */
64
    protected $wasInitialized = false;
65
66
    /**
67
     * Aspect container instance
68
     *
69
     * @var AspectContainer
70
     */
71
    protected $container;
72
73
    /**
74
     * Protected constructor is used to prevent direct creation, but allows customization if needed
75
     */
76
    protected function __construct() {}
77
78
    /**
79
     * Returns the single instance of kernel
80
     */
81
    public static function getInstance() : self
82
    {
83
        if (!self::$instance) {
84
            self::$instance = new static();
85
        }
86
87
        return self::$instance;
88
    }
89
90
    /**
91
     * Init the kernel and make adjustments
92
     *
93
     * @param array $options Associative array of options for kernel
94
     */
95
    public function init(array $options = [])
96
    {
97
        if ($this->wasInitialized) {
98
            return;
99
        }
100
101
        $this->options = $this->normalizeOptions($options);
102
        define('AOP_ROOT_DIR', $this->options['appDir']);
103
        define('AOP_CACHE_DIR', $this->options['cacheDir']);
104
105
        /** @var $container AspectContainer */
106
        $container = $this->container = new $this->options['containerClass'];
107
        $container->set('kernel', $this);
108
        $container->set('kernel.interceptFunctions', $this->hasFeature(Features::INTERCEPT_FUNCTIONS));
109
        $container->set('kernel.options', $this->options);
110
111
        SourceTransformingLoader::register();
112
113
        foreach ($this->registerTransformers() as $sourceTransformer) {
114
            SourceTransformingLoader::addTransformer($sourceTransformer);
115
        }
116
117
        // Register kernel resources in the container for debug mode
118
        if ($this->options['debug']) {
119
            $this->addKernelResourcesToContainer($container);
120
        }
121
122
        AopComposerLoader::init($this->options, $container);
123
124
        // Register all AOP configuration in the container
125
        $this->configureAop($container);
126
127
        $this->wasInitialized = true;
128
    }
129
130
    /**
131
     * Returns an aspect container
132
     */
133
    public function getContainer() : AspectContainer
134
    {
135
        return $this->container;
136
    }
137
138
    /**
139
     * Checks if kernel configuration has enabled specific feature
140
     *
141
     * @param integer $featureToCheck See Go\Aop\Features enumeration class for features
142
     *
143
     * @return bool Whether specific feature enabled or not
144
     */
145
    public function hasFeature(int $featureToCheck) : bool
146
    {
147
        return ($this->options['features'] & $featureToCheck) !== 0;
148
    }
149
150
    /**
151
     * Returns list of kernel options
152
     */
153
    public function getOptions() : array
154
    {
155
        return $this->options;
156
    }
157
158
    /**
159
     * Returns default options for kernel. Available options:
160
     *
161
     *   debug    - boolean Determines whether or not kernel is in debug mode
162
     *   appDir   - string Path to the application root directory.
163
     *   cacheDir - string Path to the cache directory where compiled classes will be stored
164
     *   cacheFileMode - integer Binary mask of permission bits that is set to cache files
165
     *   features - integer Binary mask of features
166
     *   includePaths - array Whitelist of directories where aspects should be applied. Empty for everywhere.
167
     *   excludePaths - array Blacklist of directories or files where aspects shouldn't be applied.
168
     */
169
    protected function getDefaultOptions() : array
170
    {
171
        return [
172
            'debug'          => false,
173
            'appDir'         => __DIR__ . '/../../../../../',
174
            'cacheDir'       => null,
175
            'cacheFileMode'  => 0770 & ~umask(), // Respect user umask() policy
176
            'features'       => 0,
177
            'includePaths'   => [],
178
            'excludePaths'   => [],
179
            'containerClass' => static::$containerClass,
180
        ];
181
    }
182
183
184
    /**
185
     * Normalizes options for the kernel
186
     *
187
     * @param array $options List of options
188
     *
189
     * @return array
190
     */
191
    protected function normalizeOptions(array $options) : array
192
    {
193
        $options = array_replace($this->getDefaultOptions(), $options);
194
        if ($options['cacheDir']) {
195
            $options['excludePaths'][] = $options['cacheDir'];
196
        }
197
        $options['excludePaths'][] = __DIR__ . '/../';
198
199
        $options['appDir']   = PathResolver::realpath($options['appDir']);
200
        $options['cacheDir'] = PathResolver::realpath($options['cacheDir']);
201
        $options['cacheFileMode'] = (int) $options['cacheFileMode'];
202
        $options['includePaths']  = PathResolver::realpath($options['includePaths']);
203
        $options['excludePaths']  = PathResolver::realpath($options['excludePaths']);
204
205
        return $options;
206
    }
207
208
    /**
209
     * Configure an AspectContainer with advisors, aspects and pointcuts
210
     *
211
     * @param AspectContainer $container
212
     *
213
     * @return void
214
     */
215
    abstract protected function configureAop(AspectContainer $container);
216
217
    /**
218
     * Returns list of source transformers, that will be applied to the PHP source
219
     *
220
     * @return array|SourceTransformer[]
221
     */
222
    protected function registerTransformers()
223
    {
224
        $cacheManager     = $this->getContainer()->get('aspect.cache.path.manager');
225
        $filterInjector   = new FilterInjectorTransformer($this, SourceTransformingLoader::getId(), $cacheManager);
226
        $magicTransformer = new MagicConstantTransformer($this);
227
        $aspectKernel     = $this;
228
229
        $sourceTransformers = function () use ($filterInjector, $magicTransformer, $aspectKernel, $cacheManager) {
230
            $transformers    = [];
231
            $aspectContainer = $aspectKernel->getContainer();
232
            $transformers[]  = new WeavingTransformer(
233
                $aspectKernel,
234
                $aspectContainer->get('aspect.advice_matcher'),
235
                $cacheManager,
236
                $aspectContainer->get('aspect.cached.loader')
237
            );
238
            if ($aspectKernel->hasFeature(Features::INTERCEPT_INCLUDES)) {
239
                $transformers[] = $filterInjector;
240
            }
241
            $transformers[] = $magicTransformer;
242
243
            if ($aspectKernel->hasFeature(Features::INTERCEPT_INITIALIZATIONS)) {
244
                $transformers[] = new ConstructorExecutionTransformer();
245
            }
246
247
            return $transformers;
248
        };
249
250
        return [
251
            new CachingTransformer($this, $sourceTransformers, $cacheManager)
252
        ];
253
    }
254
255
    /**
256
     * Add resources for kernel
257
     *
258
     * @param AspectContainer $container
259
     */
260
    protected function addKernelResourcesToContainer(AspectContainer $container)
261
    {
262
        $trace    = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
263
        $refClass = new \ReflectionObject($this);
264
265
        $container->addResource($trace[1]['file']);
266
        $container->addResource($refClass->getFileName());
267
    }
268
}
269