| 1 |
<?php |
| 2 |
|
| 3 |
/** |
| 4 |
* This file is part of the Nette Framework (https://nette.org) |
| 5 |
* Copyright (c) 2004 David Grudl (https://davidgrudl.com) |
| 6 |
*/ |
| 7 |
declare (strict_types=1); |
| 8 |
namespace Packetery\Nette\DI\Extensions; |
| 9 |
|
| 10 |
use Packetery\Nette; |
| 11 |
use Packetery\Nette\Loaders\RobotLoader; |
| 12 |
use Packetery\Nette\Schema\Expect; |
| 13 |
use Packetery\Nette\Utils\Arrays; |
| 14 |
/** |
| 15 |
* Services auto-discovery. |
| 16 |
*/ |
| 17 |
final class SearchExtension extends \Packetery\Nette\DI\CompilerExtension |
| 18 |
{ |
| 19 |
/** @var array */ |
| 20 |
private $classes = []; |
| 21 |
/** @var string */ |
| 22 |
private $tempDir; |
| 23 |
public function __construct(string $tempDir) |
| 24 |
{ |
| 25 |
$this->tempDir = $tempDir; |
| 26 |
} |
| 27 |
public function getConfigSchema() : \Packetery\Nette\Schema\Schema |
| 28 |
{ |
| 29 |
return Expect::arrayOf(Expect::structure(['in' => Expect::string()->required(), 'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'exclude' => Expect::structure(['classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([])]), 'tags' => Expect::array()]))->before(function ($val) { |
| 30 |
return \is_string($val['in'] ?? null) ? ['default' => $val] : $val; |
| 31 |
}); |
| 32 |
} |
| 33 |
public function loadConfiguration() |
| 34 |
{ |
| 35 |
foreach (\array_filter($this->config) as $name => $batch) { |
| 36 |
if (!\is_dir($batch->in)) { |
| 37 |
throw new \Packetery\Nette\DI\InvalidConfigurationException(\sprintf("Option '%s › %s › in' must be valid directory name, '%s' given.", $this->name, $name, $batch->in)); |
| 38 |
} |
| 39 |
foreach ($this->findClasses($batch) as $class) { |
| 40 |
$this->classes[$class] = \array_merge($this->classes[$class] ?? [], $batch->tags); |
| 41 |
} |
| 42 |
} |
| 43 |
} |
| 44 |
public function findClasses(\stdClass $config) : array |
| 45 |
{ |
| 46 |
$robot = new RobotLoader(); |
| 47 |
$robot->setTempDirectory($this->tempDir); |
| 48 |
$robot->addDirectory($config->in); |
| 49 |
$robot->acceptFiles = $config->files ?: ['*.php']; |
| 50 |
$robot->reportParseErrors(\false); |
| 51 |
$robot->refresh(); |
| 52 |
$classes = \array_unique(\array_keys($robot->getIndexedClasses())); |
| 53 |
$exclude = $config->exclude; |
| 54 |
$acceptRE = self::buildNameRegexp($config->classes); |
| 55 |
$rejectRE = self::buildNameRegexp($exclude->classes); |
| 56 |
$acceptParent = \array_merge($config->extends, $config->implements); |
| 57 |
$rejectParent = \array_merge($exclude->extends, $exclude->implements); |
| 58 |
$found = []; |
| 59 |
foreach ($classes as $class) { |
| 60 |
if (!\class_exists($class) && !\interface_exists($class) && !\trait_exists($class)) { |
| 61 |
throw new \Packetery\Nette\InvalidStateException(\sprintf('Class %s was found, but it cannot be loaded by autoloading.', $class)); |
| 62 |
} |
| 63 |
$rc = new \ReflectionClass($class); |
| 64 |
if (($rc->isInstantiable() || $rc->isInterface() && \count($methods = $rc->getMethods()) === 1 && \in_array($methods[0]->name, ['get', 'create'], \true)) && (!$acceptRE || \preg_match($acceptRE, $rc->name)) && (!$rejectRE || !\preg_match($rejectRE, $rc->name)) && (!$acceptParent || Arrays::some($acceptParent, function ($nm) use($rc) { |
| 65 |
return $rc->isSubclassOf($nm); |
| 66 |
})) && (!$rejectParent || Arrays::every($rejectParent, function ($nm) use($rc) { |
| 67 |
return !$rc->isSubclassOf($nm); |
| 68 |
}))) { |
| 69 |
$found[] = $rc->name; |
| 70 |
} |
| 71 |
} |
| 72 |
return $found; |
| 73 |
} |
| 74 |
public function beforeCompile() |
| 75 |
{ |
| 76 |
$builder = $this->getContainerBuilder(); |
| 77 |
foreach ($this->classes as $class => $foo) { |
| 78 |
if ($builder->findByType($class)) { |
| 79 |
unset($this->classes[$class]); |
| 80 |
} |
| 81 |
} |
| 82 |
foreach ($this->classes as $class => $tags) { |
| 83 |
if (\class_exists($class)) { |
| 84 |
$def = $builder->addDefinition(null)->setType($class); |
| 85 |
} elseif (\method_exists($class, 'create')) { |
| 86 |
$def = $builder->addFactoryDefinition(null)->setImplement($class); |
| 87 |
} else { |
| 88 |
$def = $builder->addAccessorDefinition(null)->setImplement($class); |
| 89 |
} |
| 90 |
$def->setTags(Arrays::normalize($tags, \true)); |
| 91 |
} |
| 92 |
} |
| 93 |
private static function buildNameRegexp(array $masks) : ?string |
| 94 |
{ |
| 95 |
$res = []; |
| 96 |
foreach ($masks as $mask) { |
| 97 |
$mask = (\strpos($mask, '\\') === \false ? '**\\' : '') . $mask; |
| 98 |
$mask = \preg_quote($mask, '#'); |
| 99 |
$mask = \str_replace('\\*\\*\\\\', '(.*\\\\)?', $mask); |
| 100 |
$mask = \str_replace('\\\\\\*\\*', '(\\\\.*)?', $mask); |
| 101 |
$mask = \str_replace('\\*', '\\w*', $mask); |
| 102 |
$res[] = $mask; |
| 103 |
} |
| 104 |
return $res ? '#^(' . \implode('|', $res) . ')$#i' : null; |
| 105 |
} |
| 106 |
} |
| 107 |
|