Completed
Pull Request — master (#137)
by Jáchym
02:03
created

TranslationExtension::loadExtractors()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Importance

Changes 0
Metric Value
dl 10
loc 10
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 6
nc 2
nop 0
1
<?php
2
3
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Translation\DI;
12
13
use Kdyby;
14
use Kdyby\Translation\InvalidResourceException;
15
use Nette;
16
use Nette\DI\Statement;
17
use Nette\PhpGenerator as Code;
18
use Nette\Reflection;
19
use Nette\Utils\Callback;
20
use Nette\Utils\Finder;
21
use Nette\Utils\Strings;
22
use Nette\Utils\Validators;
23
use Symfony\Component\Translation\Loader\LoaderInterface;
24
use Tracy;
25
26
27
28
/**
29
 * @author Filip Procházka <[email protected]>
30
 */
31
class TranslationExtension extends Nette\DI\CompilerExtension
32
{
33
34
	/** @deprecated */
35
	const LOADER_TAG = self::TAG_LOADER;
36
	/** @deprecated */
37
	const DUMPER_TAG = self::TAG_DUMPER;
38
	/** @deprecated */
39
	const EXTRACTOR_TAG = self::TAG_EXTRACTOR;
40
41
	const TAG_LOADER = 'translation.loader';
42
	const TAG_DUMPER = 'translation.dumper';
43
	const TAG_EXTRACTOR = 'translation.extractor';
44
45
	const RESOLVER_REQUEST = 'request';
46
	const RESOLVER_HEADER = 'header';
47
	const RESOLVER_SESSION = 'session';
48
49
	/**
50
	 * @var array
51
	 */
52
	public $defaults = [
53
		'whitelist' => NULL, // array('cs', 'en'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
54
		'default' => 'en',
55
		'logging' => NULL, //  TRUE for psr/log, or string for kdyby/monolog channel
56
		// 'fallback' => array('en_US', 'en'), // using custom merge strategy becase Nette's config merger appends lists of values
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
57
		'dirs' => ['%appDir%/lang', '%appDir%/locale'],
58
		'cache' => 'Kdyby\Translation\Caching\PhpFileStorage',
59
		'debugger' => '%debugMode%',
60
		'resolvers' => [
61
			self::RESOLVER_SESSION => FALSE,
62
			self::RESOLVER_REQUEST => TRUE,
63
			self::RESOLVER_HEADER => TRUE,
64
		],
65
		'loaders' => []
66
	];
67
68
	/**
69
	 * @var array
70
	 */
71
	private $loaders;
72
73
74
75
	public function __construct()
76
	{
77
		$this->defaults['cache'] = new Statement($this->defaults['cache'], ['%tempDir%/cache']);
78
	}
79
80
81
82
	public function loadConfiguration()
83
	{
84
		$this->loaders = [];
85
86
		$builder = $this->getContainerBuilder();
87
		$config = $this->getConfig();
88
89
		$translator = $builder->addDefinition($this->prefix('default'))
90
			->setClass('Kdyby\Translation\Translator', [$this->prefix('@userLocaleResolver')])
91
			->addSetup('?->setTranslator(?)', [$this->prefix('@userLocaleResolver.param'), '@self'])
92
			->addSetup('setDefaultLocale', [$config['default']])
93
			->addSetup('setLocaleWhitelist', [$config['whitelist']])
94
			->setInject(FALSE);
95
96
		Validators::assertField($config, 'fallback', 'list');
97
		$translator->addSetup('setFallbackLocales', [$config['fallback']]);
98
99
		$catalogueCompiler = $builder->addDefinition($this->prefix('catalogueCompiler'))
100
			->setClass('Kdyby\Translation\CatalogueCompiler', self::filterArgs($config['cache']))
101
			->setInject(FALSE);
102
103
		if ($config['debugger'] && interface_exists('Tracy\IBarPanel')) {
104
			$builder->addDefinition($this->prefix('panel'))
105
				->setClass('Kdyby\Translation\Diagnostics\Panel', [dirname($builder->expand('%appDir%'))])
106
				->addSetup('setLocaleWhitelist', [$config['whitelist']]);
107
108
			$translator->addSetup('?->register(?)', [$this->prefix('@panel'), '@self']);
109
			$catalogueCompiler->addSetup('enableDebugMode');
110
		}
111
112
		$this->loadLocaleResolver($config);
113
114
		$builder->addDefinition($this->prefix('helpers'))
115
			->setClass('Kdyby\Translation\TemplateHelpers')
116
			->setFactory($this->prefix('@default') . '::createTemplateHelpers')
117
			->setInject(FALSE);
118
119
		$builder->addDefinition($this->prefix('fallbackResolver'))
120
			->setClass('Kdyby\Translation\FallbackResolver')
121
			->setInject(FALSE);
122
123
		$builder->addDefinition($this->prefix('catalogueFactory'))
124
			->setClass('Kdyby\Translation\CatalogueFactory')
125
			->setInject(FALSE);
126
127
		$builder->addDefinition($this->prefix('selector'))
128
			->setClass('Symfony\Component\Translation\MessageSelector')
129
			->setInject(FALSE);
130
131
		$builder->addDefinition($this->prefix('extractor'))
132
			->setClass('Symfony\Component\Translation\Extractor\ChainExtractor')
133
			->setInject(FALSE);
134
135
		$this->loadExtractors();
136
137
		$builder->addDefinition($this->prefix('writer'))
138
			->setClass('Symfony\Component\Translation\Writer\TranslationWriter')
139
			->setInject(FALSE);
140
141
		$this->loadDumpers();
142
143
		$builder->addDefinition($this->prefix('loader'))
144
			->setClass('Kdyby\Translation\TranslationLoader')
145
			->setInject(FALSE);
146
147
		$loaders = $this->loadFromFile(__DIR__ . '/config/loaders.neon');
148
		$this->loadLoaders($loaders, $config['loaders'] ? : array_keys($loaders));
149
150
		if ($this->isRegisteredConsoleExtension()) {
151
			$this->loadConsole($config);
152
		}
153
	}
154
155
156
157
	protected function loadLocaleResolver(array $config)
158
	{
159
		$builder = $this->getContainerBuilder();
160
161
		$builder->addDefinition($this->prefix('userLocaleResolver.param'))
162
			->setClass('Kdyby\Translation\LocaleResolver\LocaleParamResolver')
163
			->setAutowired(FALSE)
164
			->setInject(FALSE);
165
166
		$builder->addDefinition($this->prefix('userLocaleResolver.acceptHeader'))
167
			->setClass('Kdyby\Translation\LocaleResolver\AcceptHeaderResolver')
168
			->setInject(FALSE);
169
170
		$builder->addDefinition($this->prefix('userLocaleResolver.session'))
171
			->setClass('Kdyby\Translation\LocaleResolver\SessionResolver')
172
			->setInject(FALSE);
173
174
		$chain = $builder->addDefinition($this->prefix('userLocaleResolver'))
175
			->setClass('Kdyby\Translation\IUserLocaleResolver')
176
			->setFactory('Kdyby\Translation\LocaleResolver\ChainResolver')
177
			->setInject(FALSE);
178
179
		$resolvers = [];
180 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_HEADER]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
181
			$resolvers[] = $this->prefix('@userLocaleResolver.acceptHeader');
182
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.acceptHeader')]);
183
		}
184
185 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_REQUEST]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
			$resolvers[] = $this->prefix('@userLocaleResolver.param');
187
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.param')]);
188
		}
189
190 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_SESSION]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
			$resolvers[] = $this->prefix('@userLocaleResolver.session');
192
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.session')]);
193
		}
194
195
		if ($config['debugger'] && interface_exists('Tracy\IBarPanel')) {
196
			$builder->getDefinition($this->prefix('panel'))
197
				->addSetup('setLocaleResolvers', [array_reverse($resolvers)]);
198
		}
199
	}
200
201
202
203
	protected function loadConsole(array $config)
204
	{
205
		$builder = $this->getContainerBuilder();
206
207
		Validators::assertField($config, 'dirs', 'list');
208
		$builder->addDefinition($this->prefix('console.extract'))
209
			->setClass('Kdyby\Translation\Console\ExtractCommand')
210
			->addSetup('$defaultOutputDir', [reset($config['dirs'])])
211
			->setInject(FALSE)
212
			->addTag('kdyby.console.command', 'latte');
213
	}
214
215
216
217 View Code Duplication
	protected function loadDumpers()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
218
	{
219
		$builder = $this->getContainerBuilder();
220
221
		foreach ($this->loadFromFile(__DIR__ . '/config/dumpers.neon') as $format => $class) {
222
			$builder->addDefinition($this->prefix('dumper.' . $format))
223
				->setClass($class)
224
				->addTag(self::TAG_DUMPER, $format);
225
		}
226
	}
227
228
229
230
	protected function loadLoaders(array $loaders, array $allowed)
231
	{
232
		$builder = $this->getContainerBuilder();
233
234
		foreach ($loaders as $format => $class) {
235
			if (array_search($format, $allowed) === FALSE) {
236
				continue;
237
			}
238
			$builder->addDefinition($this->prefix('loader.' . $format))
239
				->setClass($class)
240
				->addTag(self::TAG_LOADER, $format);
241
		}
242
	}
243
244
245
246 View Code Duplication
	protected function loadExtractors()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
247
	{
248
		$builder = $this->getContainerBuilder();
249
250
		foreach ($this->loadFromFile(__DIR__ . '/config/extractors.neon') as $format => $class) {
251
			$builder->addDefinition($this->prefix('extractor.' . $format))
252
				->setClass($class)
253
				->addTag(self::TAG_EXTRACTOR, $format);
254
		}
255
	}
256
257
258
259
	public function beforeCompile()
260
	{
261
		$builder = $this->getContainerBuilder();
262
		$config = $this->getConfig();
263
264
		$this->beforeCompileLogging($config);
265
266
		$registerToLatte = function (Nette\DI\ServiceDefinition $def) {
267
			$def->addSetup('?->onCompile[] = function($engine) { Kdyby\Translation\Latte\TranslateMacros::install($engine->getCompiler()); }', ['@self']);
268
269
			if (method_exists('Latte\Engine', 'addProvider')) { // Nette 2.4
270
				$def->addSetup('addProvider', ['translator', $this->prefix('@default')])
271
					->addSetup('addFilter', ['translate', [$this->prefix('@helpers'), 'translateFilterAware']]);
272
			} else {
273
				$def->addSetup('addFilter', ['getTranslator', [$this->prefix('@helpers'), 'getTranslator']])
274
					->addSetup('addFilter', ['translate', [$this->prefix('@helpers'), 'translate']]);
275
			}
276
		};
277
278
		$latteFactoryService = $builder->getByType('Nette\Bridges\ApplicationLatte\ILatteFactory');
279
		if (!$latteFactoryService || !self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), 'Latte\engine')) {
280
			$latteFactoryService = 'nette.latteFactory';
281
		}
282
283
		if ($builder->hasDefinition($latteFactoryService) && self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), 'Latte\Engine')) {
284
			$registerToLatte($builder->getDefinition($latteFactoryService));
285
		}
286
287
		if ($builder->hasDefinition('nette.latte')) {
288
			$registerToLatte($builder->getDefinition('nette.latte'));
289
		}
290
291
		$applicationService = $builder->getByType('Nette\Application\Application') ?: 'application';
292
		if ($builder->hasDefinition($applicationService)) {
293
			$builder->getDefinition($applicationService)
294
				->addSetup('$service->onRequest[] = ?', [[$this->prefix('@userLocaleResolver.param'), 'onRequest']]);
295
296
			if ($config['debugger'] && interface_exists('Tracy\IBarPanel')) {
297
				$builder->getDefinition($applicationService)
298
					->addSetup('$self = $this; $service->onStartup[] = function () use ($self) { $self->getService(?); }', [$this->prefix('default')])
299
					->addSetup('$service->onRequest[] = ?', [[$this->prefix('@panel'), 'onRequest']]);
300
			}
301
		}
302
303
		if (class_exists('Tracy\Debugger')) {
304
			Kdyby\Translation\Diagnostics\Panel::registerBluescreen();
305
		}
306
307
		$extractor = $builder->getDefinition($this->prefix('extractor'));
308 View Code Duplication
		foreach ($builder->findByTag(self::TAG_EXTRACTOR) as $extractorId => $meta) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
309
			Validators::assert($meta, 'string:2..');
310
311
			$extractor->addSetup('addExtractor', [$meta, '@' . $extractorId]);
312
313
			$builder->getDefinition($extractorId)->setAutowired(FALSE)->setInject(FALSE);
314
		}
315
316
		$writer = $builder->getDefinition($this->prefix('writer'));
317 View Code Duplication
		foreach ($builder->findByTag(self::TAG_DUMPER) as $dumperId => $meta) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
318
			Validators::assert($meta, 'string:2..');
319
320
			$writer->addSetup('addDumper', [$meta, '@' . $dumperId]);
321
322
			$builder->getDefinition($dumperId)->setAutowired(FALSE)->setInject(FALSE);
323
		}
324
325
		$this->loaders = [];
326
		foreach ($builder->findByTag(self::TAG_LOADER) as $loaderId => $meta) {
327
			Validators::assert($meta, 'string:2..');
328
			$builder->getDefinition($loaderId)->setAutowired(FALSE)->setInject(FALSE);
329
			$this->loaders[$meta] = $loaderId;
330
		}
331
332
		$builder->getDefinition($this->prefix('loader'))
333
			->addSetup('injectServiceIds', [$this->loaders])
334
			->setInject(FALSE);
335
336
		foreach ($this->compiler->getExtensions() as $extension) {
337
			if (!$extension instanceof ITranslationProvider) {
338
				continue;
339
			}
340
341
			$config['dirs'] = array_merge($config['dirs'], array_values($extension->getTranslationResources()));
342
		}
343
344
		if ($dirs = array_values(array_filter($config['dirs'], Callback::closure('is_dir')))) {
345
			foreach ($dirs as $dir) {
346
				$builder->addDependency($dir);
347
			}
348
349
			$this->loadResourcesFromDirs($dirs);
350
		}
351
	}
352
353
354
355
	protected function beforeCompileLogging(array $config)
356
	{
357
		$builder = $this->getContainerBuilder();
358
		$translator = $builder->getDefinition($this->prefix('default'));
359
360
		if ($config['logging'] === TRUE) {
361
			$translator->addSetup('injectPsrLogger');
362
363
		} elseif (is_string($config['logging'])) { // channel for kdyby/monolog
364
			$translator->addSetup('injectPsrLogger', [
365
				new Statement('@Kdyby\Monolog\Logger::channel', [$config['logging']]),
366
			]);
367
368
		} elseif ($config['logging'] !== NULL) {
369
			throw new Kdyby\Translation\InvalidArgumentException(sprintf(
370
				"Invalid config option for logger. Valid are TRUE for general psr/log or string for kdyby/monolog channel, but %s was given",
371
				$config['logging']
372
			));
373
		}
374
	}
375
376
377
378
	protected function loadResourcesFromDirs($dirs)
379
	{
380
		$builder = $this->getContainerBuilder();
381
		$config = $this->getConfig();
382
383
		$whitelistRegexp = Kdyby\Translation\Translator::buildWhitelistRegexp($config['whitelist']);
384
		$translator = $builder->getDefinition($this->prefix('default'));
385
386
		$mask = array_map(function ($value) {
387
			return '*.*.' . $value;
388
		}, array_keys($this->loaders));
389
390
		foreach (Finder::findFiles($mask)->from($dirs) as $file) {
391
			/** @var \SplFileInfo $file */
392
			if (!$m = Strings::match($file->getFilename(), '~^(?P<domain>.*?)\.(?P<locale>[^\.]+)\.(?P<format>[^\.]+)$~')) {
393
				continue;
394
			}
395
396
			if ($whitelistRegexp && !preg_match($whitelistRegexp, $m['locale']) && $builder->parameters['productionMode']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $whitelistRegexp of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
397
				continue; // ignore in production mode, there is no need to pass the ignored resources
398
			}
399
400
			$this->validateResource($m['format'], $file->getPathname(), $m['locale'], $m['domain']);
401
			$translator->addSetup('addResource', [$m['format'], $file->getPathname(), $m['locale'], $m['domain']]);
402
			$builder->addDependency($file->getPathname());
403
		}
404
	}
405
406
407
408
	/**
409
	 * @param string $format
410
	 * @param string $file
411
	 * @param string $locale
412
	 * @param string $domain
413
	 */
414
	protected function validateResource($format, $file, $locale, $domain)
415
	{
416
		$builder = $this->getContainerBuilder();
417
418
		if (!isset($this->loaders[$format])) {
419
			return;
420
		}
421
422
		try {
423
			$def = $builder->getDefinition($this->loaders[$format]);
424
			$refl = Reflection\ClassType::from($def->getEntity() ?: $def->getClass());
425
			if (($method = $refl->getConstructor()) && $method->getNumberOfRequiredParameters() > 1) {
426
				return;
427
			}
428
429
			$loader = $refl->newInstance();
430
			if (!$loader instanceof LoaderInterface) {
0 ignored issues
show
Bug introduced by
The class Symfony\Component\Transl...\Loader\LoaderInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
431
				return;
432
			}
433
434
		} catch (\ReflectionException $e) {
435
			return;
436
		}
437
438
		try {
439
			$loader->load($file, $locale, $domain);
440
441
		} catch (\Exception $e) {
442
			throw new InvalidResourceException("Resource $file is not valid and cannot be loaded.", 0, $e);
443
		}
444
	}
445
446
447
448
	public function afterCompile(Code\ClassType $class)
449
	{
450
		$initialize = $class->getMethod('initialize');
451
		if (class_exists('Tracy\Debugger')) {
452
			$initialize->addBody('Kdyby\Translation\Diagnostics\Panel::registerBluescreen();');
453
		}
454
	}
455
456
457
458
	private function isRegisteredConsoleExtension()
459
	{
460
		foreach ($this->compiler->getExtensions() as $extension) {
461
			if ($extension instanceof Kdyby\Console\DI\ConsoleExtension) {
0 ignored issues
show
Bug introduced by
The class Kdyby\Console\DI\ConsoleExtension does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
462
				return TRUE;
463
			}
464
		}
465
466
		return FALSE;
467
	}
468
469
470
471
	/**
472
	 * {@inheritdoc}
473
	 */
474
	public function getConfig(array $defaults = NULL, $expand = TRUE)
475
	{
476
		return parent::getConfig($this->defaults) + ['fallback' => ['en_US']];
477
	}
478
479
480
481
	/**
482
	 * @param string|\stdClass $statement
483
	 * @return Nette\DI\Statement[]
484
	 */
485
	public static function filterArgs($statement)
486
	{
487
		return Nette\DI\Compiler::filterArguments([is_string($statement) ? new Nette\DI\Statement($statement) : $statement]);
488
	}
489
490
491
492
	/**
493
	 * @param \Nette\Configurator $configurator
494
	 */
495
	public static function register(Nette\Configurator $configurator)
496
	{
497
		$configurator->onCompile[] = function ($config, Nette\DI\Compiler $compiler) {
498
			$compiler->addExtension('translation', new TranslationExtension());
499
		};
500
	}
501
502
503
504
	/**
505
	 * @param string $class
506
	 * @param string $type
507
	 * @return bool
508
	 */
509
	private static function isOfType($class, $type)
510
	{
511
		return $class === $type || is_subclass_of($class, $type);
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $type can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
512
	}
513
514
}
515