• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / di / 4292649159

pending completion
4292649159

push

github

David Grudl
more self explanatory message for factory and service mismatch (closes #199) (#284)

8 of 8 new or added lines in 1 file covered. (100.0%)

2086 of 2207 relevant lines covered (94.52%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.44
/src/DI/Extensions/SearchExtension.php
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

8
declare(strict_types=1);
9

10
namespace Nette\DI\Extensions;
11

12
use Nette;
13
use Nette\Loaders\RobotLoader;
14
use Nette\Schema\Expect;
15
use Nette\Utils\Arrays;
16

17

18
/**
19
 * Services auto-discovery.
20
 */
21
final class SearchExtension extends Nette\DI\CompilerExtension
22
{
23
        private array $classes = [];
24
        private string $tempDir;
25

26

27
        public function __construct(string $tempDir)
1✔
28
        {
29
                $this->tempDir = $tempDir;
1✔
30
        }
1✔
31

32

33
        public function getConfigSchema(): Nette\Schema\Schema
34
        {
35
                return Expect::arrayOf(
1✔
36
                        Expect::structure([
1✔
37
                                'in' => Expect::string()->required(),
1✔
38
                                'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
39
                                'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
40
                                'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
41
                                'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
42
                                'exclude' => Expect::structure([
1✔
43
                                        'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
44
                                        'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
45
                                        'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
46
                                        'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
1✔
47
                                ]),
48
                                'tags' => Expect::array(),
1✔
49
                        ]),
50
                )->before(fn($val) => is_string($val['in'] ?? null)
1✔
51
                                ? ['default' => $val]
1✔
52
                                : $val);
1✔
53
        }
54

55

56
        public function loadConfiguration()
57
        {
58
                foreach (array_filter($this->config) as $name => $batch) {
1✔
59
                        if (!is_dir($batch->in)) {
1✔
60
                                throw new Nette\DI\InvalidConfigurationException(sprintf(
1✔
61
                                        "Option '%s\u{a0}›\u{a0}%s\u{a0}›\u{a0}in' must be valid directory name, '%s' given.",
1✔
62
                                        $this->name,
1✔
63
                                        $name,
64
                                        $batch->in,
1✔
65
                                ));
66
                        }
67

68
                        foreach ($this->findClasses($batch) as $class) {
1✔
69
                                $this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags);
1✔
70
                        }
71
                }
72
        }
1✔
73

74

75
        public function findClasses(\stdClass $config): array
1✔
76
        {
77
                $exclude = $config->exclude;
1✔
78
                $robot = new RobotLoader;
1✔
79
                $robot->setTempDirectory($this->tempDir);
1✔
80
                $robot->addDirectory($config->in);
1✔
81
                $robot->acceptFiles = $config->files ?: ['*.php'];
1✔
82
                $robot->ignoreDirs = array_merge($robot->ignoreDirs, $exclude->files);
1✔
83
                $robot->reportParseErrors(false);
1✔
84
                $robot->refresh();
1✔
85
                $classes = array_unique(array_keys($robot->getIndexedClasses()));
1✔
86

87
                $acceptRE = self::buildNameRegexp($config->classes);
1✔
88
                $rejectRE = self::buildNameRegexp($exclude->classes);
1✔
89
                $acceptParent = array_merge($config->extends, $config->implements);
1✔
90
                $rejectParent = array_merge($exclude->extends, $exclude->implements);
1✔
91

92
                $found = [];
1✔
93
                foreach ($classes as $class) {
1✔
94
                        if (!class_exists($class) && !interface_exists($class) && !trait_exists($class)) {
1✔
95
                                throw new Nette\InvalidStateException(sprintf(
×
96
                                        'Class %s was found, but it cannot be loaded by autoloading.',
×
97
                                        $class,
98
                                ));
99
                        }
100

101
                        $rc = new \ReflectionClass($class);
1✔
102
                        if (
103
                                ($rc->isInstantiable()
1✔
104
                                        ||
105
                                        ($rc->isInterface()
1✔
106
                                        && count($methods = $rc->getMethods()) === 1
1✔
107
                                        && $methods[0]->name === 'create')
1✔
108
                                )
109
                                && (!$acceptRE || preg_match($acceptRE, $rc->name))
1✔
110
                                && (!$rejectRE || !preg_match($rejectRE, $rc->name))
1✔
111
                                && (!$acceptParent || Arrays::some($acceptParent, fn($nm) => $rc->isSubclassOf($nm)))
1✔
112
                                && (!$rejectParent || Arrays::every($rejectParent, fn($nm) => !$rc->isSubclassOf($nm)))
1✔
113
                        ) {
114
                                $found[] = $rc->name;
1✔
115
                        }
116
                }
117

118
                return $found;
1✔
119
        }
120

121

122
        public function beforeCompile()
123
        {
124
                $builder = $this->getContainerBuilder();
1✔
125

126
                foreach ($this->classes as $class => $foo) {
1✔
127
                        if ($builder->findByType($class)) {
1✔
128
                                unset($this->classes[$class]);
1✔
129
                        }
130
                }
131

132
                foreach ($this->classes as $class => $tags) {
1✔
133
                        $def = class_exists($class)
1✔
134
                                ? $builder->addDefinition(null)->setType($class)
1✔
135
                                : $builder->addFactoryDefinition(null)->setImplement($class);
1✔
136
                        $def->setTags(Arrays::normalize($tags, true));
1✔
137
                }
138
        }
1✔
139

140

141
        private static function buildNameRegexp(array $masks): ?string
1✔
142
        {
143
                $res = [];
1✔
144
                foreach ((array) $masks as $mask) {
1✔
145
                        $mask = (str_contains($mask, '\\') ? '' : '**\\') . $mask;
1✔
146
                        $mask = preg_quote($mask, '#');
1✔
147
                        $mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask);
1✔
148
                        $mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask);
1✔
149
                        $mask = str_replace('\*', '\w*', $mask);
1✔
150
                        $res[] = $mask;
1✔
151
                }
152

153
                return $res ? '#^(' . implode('|', $res) . ')$#i' : null;
1✔
154
        }
155
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc