Completed
Push — master ( 546bff...267d3a )
by Андрей
02:26
created

ModuleOptionsPluginManager::isValidModuleOptions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * @link  https://github.com/nnx-company/module-options
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace Nnx\ModuleOptions;
7
8
use Zend\EventManager\EventManagerAwareTrait;
9
use Zend\ServiceManager\AbstractPluginManager;
10
use Nnx\ModuleOptions\Options\ModuleOptions;
11
use Zend\EventManager\EventInterface;
12
use ReflectionClass;
13
use Zend\ServiceManager\ConfigInterface;
14
use Zend\ModuleManager\ModuleManagerInterface;
15
16
/**
17
 * Class ModuleOptionsPluginManager
18
 *
19
 * @package Nnx\ModuleOptions
20
 *
21
 */
22
class ModuleOptionsPluginManager extends AbstractPluginManager implements ModuleOptionsPluginManagerInterface
23
{
24
    use EventManagerAwareTrait;
25
26
    /**
27
     * Имя секции в конфиги приложения отвечающей за настройки менеджера
28
     *
29
     * @var string
30
     */
31
    const CONFIG_KEY = 'module_options';
32
33
    /**
34
     * Прототип для создания нового объекта события
35
     *
36
     * @var ModuleOptionsEventInterface
37
     */
38
    protected $eventPrototype;
39
40
    /**
41
     * Кеш. Ключем является имя модуля, а значением объект с настройками модуля
42
     *
43
     * @var array
44
     */
45
    protected $moduleNameToModuleOptions = [];
46
47
    /**
48
     * Ключем является имя класса, а значением имя модуля, к которому принадлежит этот класс
49
     *
50
     * @var array
51
     */
52
    protected $classNameToModuleName = [];
53
54
55
    /**
56
     * Менеджер модулей
57
     *
58
     * @var ModuleManagerInterface
59
     */
60
    protected $moduleManager;
61
62
    /**
63
     * Индекс по именам модулей. Ключем является имя модуля. Значением колличество символов в имени модуля. Данные отсортированы
64
     * по возрастанию длинны имени модуля
65
     *
66
     * @var array|null
67
     */
68
    protected $modulesIndex;
69
70
    /**
71
     * ModuleOptionsPluginManager constructor.
72
     *
73
     * @param ModuleManagerInterface $moduleManager
74
     * @param ConfigInterface|null   $configuration
75
     */
76
    public function __construct(ModuleManagerInterface $moduleManager, ConfigInterface $configuration = null)
77
    {
78
        $this->setModuleManager($moduleManager);
79
        parent::__construct($configuration);
80
    }
81
82
    /**
83
     * Возвращает индекс по именам модулей.
84
     *
85
     *
86
     * @return array
87
     */
88
    public function getModulesIndex()
89
    {
90
        if ($this->modulesIndex) {
91
            return $this->modulesIndex;
92
        }
93
94
        $modules = $this->getModuleManager()->getModules();
95
96
        $modulesIndex = [];
97
98
        foreach ($modules as $moduleName) {
99
            $prepareModuleName = rtrim($moduleName, '\\') . '\\';
100
            $modulesIndex[$prepareModuleName] = strlen($prepareModuleName);
101
        }
102
103
        arsort($modulesIndex, SORT_NUMERIC);
104
        $this->modulesIndex = $modulesIndex;
105
106
        return $this->modulesIndex;
107
    }
108
109
110
    /**
111
     * @inheritdoc
112
     *
113
     * @param string $className
114
     *
115
     * @return ModuleOptionsInterface
116
     *
117
     * @throws Exception\ErrorGetModuleOptionsByClassNameException
118
     */
119
    public function getOptionsByClassName($className)
120
    {
121
        try {
122
            $moduleName = $this->getModuleNameByClassName($className);
123
            $moduleOptions = $this->getOptionsByModuleName($moduleName);
124
        } catch (\Exception $e) {
125
            throw new Exception\ErrorGetModuleOptionsByClassNameException($e->getMessage(), $e->getCode(), $e);
126
        }
127
        return $moduleOptions;
128
    }
129
130
    /**
131
     * @inheritdoc
132
     *
133
     * @param string $className
134
     *
135
     * @return boolean
136
     *
137
     * @throws Exception\ResolveModuleNameException
138
     * @throws Exception\InvalidEventException
139
     */
140
    public function hasOptionsByClassName($className)
141
    {
142
        if (!$this->hasModuleNameByClassName($className)) {
143
            return false;
144
        }
145
146
        $moduleName = $this->getModuleNameByClassName($className);
147
148
        return $this->hasOptionsByModuleName($moduleName);
149
    }
150
151
152
    /**
153
     * @inheritdoc
154
     *
155
     * @param $className
156
     *
157
     * @return string
158
     *
159
     * @throws Exception\ResolveModuleNameException
160
     */
161
    public function getModuleNameByClassName($className)
162
    {
163
        $resultModuleName = $this->autoDetectModuleNameByClassName($className);
164
        if (null === $resultModuleName) {
165
            $errMsg = sprintf('Unable to determine the module name for a class of %s', $className);
166
            throw new Exception\ResolveModuleNameException($errMsg);
167
        }
168
169
        return $resultModuleName;
170
    }
171
172
    /**
173
     * Автоматиченское определение имени модуля, по имени любого класса входящего в этот модуль. В случае если определить
174
     * не удалось, возвращается null
175
     *
176
     * @param $className
177
     *
178
     * @return string
179
     */
180
    public function autoDetectModuleNameByClassName($className)
181
    {
182
        if (array_key_exists($className, $this->classNameToModuleName)) {
183
            $resultModuleName =  $this->classNameToModuleName[$className];
184
        } else {
185
            $index = $this->getModulesIndex();
186
            $classNameLength = strlen($className);
187
188
            $resultModuleName = null;
189
            foreach ($index as $moduleName => $moduleNameLength) {
190
                if ($classNameLength > $moduleNameLength && 0 === strrpos($className, $moduleName)) {
191
                    $resultModuleName = $moduleName;
192
                    break;
193
                }
194
            }
195
            $this->classNameToModuleName[$className] = $resultModuleName;
196
        }
197
198
        return $resultModuleName;
199
    }
200
201
    /**
202
     * @inheritdoc
203
     *
204
     * @param $className
205
     *
206
     * @return boolean
207
     */
208
    public function hasModuleNameByClassName($className)
209
    {
210
        $resultModuleName = $this->autoDetectModuleNameByClassName($className);
211
212
        return null !== $resultModuleName;
213
    }
214
215
    /**
216
     * @inheritdoc
217
     *
218
     * @param string $moduleName
219
     *
220
     * @throws Exception\InvalidEventException
221
     * @throws Exception\RuntimeException
222
     * @throws \Zend\ServiceManager\Exception\RuntimeException
223
     *
224
     * @return ModuleOptionsInterface
225
     */
226
    public function getOptionsByModuleName($moduleName)
227
    {
228
        $moduleOptions = $this->resolveModuleOptionsByModuleName($moduleName);
229
        $this->validatePlugin($moduleOptions);
230
231
        return $moduleOptions;
232
    }
233
234
    /**
235
     * Попытка получить объект ModuleOptions, на основе имени модуля. В случае если такой возможности нет, возвращается null
236
     *
237
     * @param string $moduleName
238
     *
239
     * @return mixed|null
240
     *
241
     * @throws Exception\InvalidEventException
242
     */
243
    public function resolveModuleOptionsByModuleName($moduleName)
244
    {
245
        if (array_key_exists($moduleName, $this->moduleNameToModuleOptions)) {
246
            $moduleOptions = $this->moduleNameToModuleOptions[$moduleName];
247
        } else {
248
            $event = $this->eventFactory();
249
            $event->setName(ModuleOptionsEventInterface::LOAD_MODULE_OPTIONS_EVENT);
250
            $event->setTarget($this);
251
            $event->setModuleName($moduleName);
252
253
            $eventCollections = $this->getEventManager()->trigger($event, function ($result) {
254
                return $result instanceof ModuleOptionsInterface;
255
            });
256
257
            $moduleOptionsResult = $eventCollections->last();
258
259
            $moduleOptions = $this->isValidModuleOptions($moduleOptionsResult) ? $moduleOptionsResult : null;
260
            $this->moduleNameToModuleOptions[$moduleName] = $moduleOptions;
261
        }
262
263
        return $moduleOptions;
264
    }
265
266
    /**
267
     * @inheritdoc
268
     *
269
     * @param string $moduleName
270
     *
271
     * @return boolean
272
     * @throws Exception\InvalidEventException
273
     */
274
    public function hasOptionsByModuleName($moduleName)
275
    {
276
        $moduleOptions = $this->resolveModuleOptionsByModuleName($moduleName);
277
278
        return null !== $moduleOptions;
279
    }
280
281
282
    /**
283
     * Фабрика для создания события
284
     *
285
     * @return ModuleOptionsEventInterface
286
     *
287
     * @throws Exception\InvalidEventException
288
     */
289
    public function eventFactory()
290
    {
291
        if (null !== $this->eventPrototype) {
292
            return clone $this->eventPrototype;
293
        }
294
295
        try {
296
            /** @var ModuleOptions $moduleOptions */
297
            $moduleOptions = $this->get(ModuleOptions::class);
298
299
            $moduleOptionsEventClassName = $moduleOptions->getModuleOptionsEventClassName();
300
            $r = new ReflectionClass($moduleOptionsEventClassName);
301
302
            $eventPrototype = $r->newInstance();
303
            if (!$eventPrototype instanceof EventInterface) {
304
                $errMsg = sprintf(
305
                    'Event of type %s is invalid; must implement %s',
306
                    (is_object($eventPrototype) ? get_class($eventPrototype) : gettype($eventPrototype)),
307
                    ModuleOptionsEventInterface::class
308
                );
309
                throw new Exception\RuntimeException($errMsg);
310
            }
311
            $this->eventPrototype = $eventPrototype;
312
        } catch (\Exception $e) {
313
            throw new Exception\InvalidEventException($e->getMessage(), $e->getCode(), $e);
314
        }
315
316
        return clone $this->eventPrototype;
317
    }
318
319
    /**
320
     * @inheritdoc
321
     */
322
    public function attachDefaultListeners()
323
    {
324
        $this->getEventManager()->attach(ModuleOptionsEventInterface::LOAD_MODULE_OPTIONS_EVENT, [$this, 'loadModuleOptions'], 100);
325
    }
326
327
    /**
328
     * Получение конфига модуля, по имени класса модуля
329
     *
330
     * @param ModuleOptionsEventInterface $e
331
     *
332
     * @return ModuleOptionsInterface|null
333
     *
334
     * @throws \Zend\ServiceManager\Exception\ServiceNotFoundException
335
     * @throws \Zend\ServiceManager\Exception\ServiceNotCreatedException
336
     * @throws \Zend\ServiceManager\Exception\RuntimeException
337
     */
338
    public function loadModuleOptions(ModuleOptionsEventInterface $e)
339
    {
340
        $moduleName = $e->getModuleName();
341
342
        /** @var ModuleOptions $moduleOptions */
343
        $moduleOptions = $this->get(ModuleOptions::class);
344
345
        $suffix = $moduleOptions->getModuleOptionsClassNameSuffix();
346
347
        $prepareModuleNamespace = rtrim($moduleName, '\\');
348
        $prepareSuffix = ltrim($suffix, '\\');
349
350
        $moduleOptionsServiceName = $prepareModuleNamespace . '\\' . $prepareSuffix;
351
352
        if (!$this->has($moduleOptionsServiceName)) {
353
            return null;
354
        }
355
356
        return $this->get($moduleOptionsServiceName);
357
    }
358
359
    /**
360
     * {@inheritDoc}
361
     *
362
     * @throws Exception\RuntimeException
363
     */
364
    public function validatePlugin($plugin)
365
    {
366
        if ($this->isValidModuleOptions($plugin)) {
367
            return;
368
        }
369
370
        throw new Exception\RuntimeException(sprintf(
371
            'Plugin of type %s is invalid; must implement %s',
372
            (is_object($plugin) ? get_class($plugin) : gettype($plugin)),
373
            ModuleOptionsInterface::class
374
        ));
375
    }
376
377
    /**
378
     * Проверяет что переданный объект является валидным экземпляром ModuleOptions
379
     *
380
     * @param $plugin
381
     *
382
     * @return boolean
383
     */
384
    public function isValidModuleOptions($plugin)
385
    {
386
        return $plugin instanceof ModuleOptionsInterface;
387
    }
388
389
    /**
390
     * Возвращает менеджер модулей
391
     *
392
     * @return ModuleManagerInterface
393
     */
394
    public function getModuleManager()
395
    {
396
        return $this->moduleManager;
397
    }
398
399
    /**
400
     * Устанавливает менеджер модулей
401
     *
402
     * @param ModuleManagerInterface $moduleManager
403
     *
404
     * @return $this
405
     */
406
    public function setModuleManager(ModuleManagerInterface $moduleManager)
407
    {
408
        $this->moduleManager = $moduleManager;
409
410
        return $this;
411
    }
412
}
413