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

nextras / orm / 29191903500

12 Jul 2026 12:02PM UTC coverage: 91.963% (-0.2%) from 92.123%
29191903500

Pull #820

github

web-flow
Merge 8dab47905 into fc483811c
Pull Request #820: refactor OrmExtension for NetteDI (BC break!)

135 of 158 new or added lines in 8 files covered. (85.44%)

1 existing line in 1 file now uncovered.

4348 of 4728 relevant lines covered (91.96%)

5.39 hits per line

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

85.96
/src/Bridges/NetteDI/PhpDocRepositoryFinder.php
1
<?php declare(strict_types = 1);
2

3
namespace Nextras\Orm\Bridges\NetteDI;
4

5

6
use Nette\DI\ContainerBuilder;
7
use Nette\DI\Definitions\FactoryDefinition;
8
use Nette\DI\Definitions\ServiceDefinition;
9
use Nette\Utils\Reflection;
10
use Nextras\Orm\Entity\IEntity;
11
use Nextras\Orm\Exception\InvalidStateException;
12
use Nextras\Orm\Exception\NotSupportedException;
13
use Nextras\Orm\Exception\RuntimeException;
14
use Nextras\Orm\Model\IModel;
15
use Nextras\Orm\Model\Model;
16
use Nextras\Orm\Repository\IRepository;
17
use ReflectionClass;
18

19

20
class PhpDocRepositoryFinder implements IRepositoryFinder
21
{
22
        /** @var list<DiRepositoryEntry> */
23
        private array $repositories = [];
24

25

26
        #[\Override]
27
        public function __construct(
5✔
28
                protected readonly ContainerBuilder $builder,
1✔
29
                protected readonly OrmExtension $extension,
1✔
30
                protected readonly string $modelClass,
1✔
31
        )
32
        {
33
        }
6✔
34

35

36
        #[\Override]
37
        public function registerRepositories(): void
38
        {
39
                $repositories = $this->findRepositories($this->modelClass);
6✔
40
                foreach ($repositories as $name => $className) {
6✔
41
                        $service = $this->setupRepositoryService($name, $className);
6✔
42
                        $this->repositories[] = new DiRepositoryEntry(
6✔
43
                                className: $className,
6✔
44
                                name: $name,
45
                                service: $service,
46
                        );
47
                }
48
        }
6✔
49

50

51
        #[\Override]
52
        public function resolveRepositories(): array
53
        {
54
                return $this->repositories;
6✔
55
        }
56

57

58
        /**
59
         * @param class-string<IModel> $modelClass
60
         * @return array<string, class-string<IRepository<*>>>
61
         */
62
        protected function findRepositories(string $modelClass): array
63
        {
64
                if ($modelClass === Model::class) {
6✔
65
                        throw new InvalidStateException('Your model has to inherit from ' . Model::class . '. Use compiler extension configuration - model key.');
×
66
                }
67

68
                $modelReflection = new ReflectionClass($modelClass);
6✔
69
                $classFileName = $modelReflection->getFileName();
6✔
70
                assert($classFileName !== false);
71
                $this->builder->addDependency($classFileName);
6✔
72

73
                $repositories = [];
6✔
74
                preg_match_all(
6✔
75
                        '~^  [ \t*]*  @property(?:|-read)  [ \t]+  ([^\s$]+)  [ \t]+  \$  (\w+)~mx',
6✔
76
                        (string) $modelReflection->getDocComment(), $matches, PREG_SET_ORDER
6✔
77
                );
78

79
                foreach ($matches as [, $type, $name]) {
6✔
80
                        /** @var class-string<IRepository<IEntity>> $type */
81
                        $type = Reflection::expandClassName($type, $modelReflection); // @phpstan-ignore argument.type (https://github.com/phpstan/phpstan/issues/12459#issuecomment-2607123277)
6✔
82
                        if (!class_exists($type)) {
6✔
83
                                throw new RuntimeException("Repository '{$type}' does not exist.");
×
84
                        }
85

86
                        $rc = new ReflectionClass($type);
6✔
87
                        assert($rc->implementsInterface(IRepository::class), sprintf(
88
                                'Property "%s" of class "%s" with type "%s" does not implement interface %s.',
89
                                $modelClass, $name, $type, IRepository::class
90
                        ));
91

92
                        $repositories[$name] = $type;
6✔
93
                }
94

95
                return $repositories;
6✔
96
        }
97

98

99
        /**
100
         * @param class-string<IRepository<*>> $repositoryClass
101
         */
102
        protected function setupRepositoryService(string $repositoryName, string $repositoryClass): ServiceDefinition
103
        {
104
                $serviceName = $this->extension->prefix('repositories.' . $repositoryName);
6✔
105
                if ($this->builder->hasDefinition($serviceName)) {
6✔
NEW
106
                        $service = $this->builder->getDefinition($serviceName);
×
107
                        return match (true) {
NEW
108
                                $service instanceof ServiceDefinition => $service,
×
NEW
109
                                $service instanceof FactoryDefinition => $service->getResultDefinition(),
×
NEW
110
                                default => throw new NotSupportedException("Service " . $service::class . " type is not supported by Nextras Orm.")
×
111
                        };
112
                }
113

114
                $this->setupMapperService($repositoryName, $repositoryClass);
6✔
115
                return $this->builder->addDefinition($serviceName)
6✔
116
                        ->setType($repositoryClass)
6✔
117
                        ->setArguments([
6✔
118
                                $this->extension->prefix('@mappers.' . $repositoryName),
6✔
119
                                $this->extension->prefix('@dependencyProvider'),
6✔
120
                        ]);
121
        }
122

123

124
        protected function setupMapperService(string $repositoryName, string $repositoryClass): void
125
        {
126
                $mapperName = $this->extension->prefix('mappers.' . $repositoryName);
6✔
127
                if ($this->builder->hasDefinition($mapperName)) {
6✔
128
                        return;
×
129
                }
130

131
                $mapperClass = str_replace('Repository', 'Mapper', $repositoryClass);
6✔
132
                if (!class_exists($mapperClass)) {
6✔
NEW
133
                        throw new InvalidStateException("Unknown $mapperClass mapper for '{$repositoryName}' repository.");
×
134
                }
135

136
                /** @var \stdClass $config */
137
                $config = $this->extension->getConfig();
6✔
138
                if ($config->connection !== null) {
6✔
139
                        $connection = ['connection' => $config->connection];
6✔
140
                } else {
141
                        $connection = [];
6✔
142
                }
143

144
                $this->builder->addDefinition($mapperName)
6✔
145
                        ->setType($mapperClass)
6✔
146
                        ->setArguments([
6✔
147
                                        'cache' => $this->extension->prefix('@cache'),
6✔
148
                                        'mapperCoordinator' => $this->extension->prefix('@mapperCoordinator'),
6✔
149
                                ] + $connection);
6✔
150
        }
6✔
151
}
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