Completed
Push — master ( 91676c...354384 )
by Alexander
02:23
created

AspectKernel::hasFeature()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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