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

contributte / nextras-orm-events / 6904032724

17 Nov 2023 12:34PM UTC coverage: 90.196% (-1.8%) from 92.0%
6904032724

push

github

f3l1x
Versions: open v0.10.x

46 of 51 relevant lines covered (90.2%)

0.9 hits per line

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

90.2
/src/DI/NextrasOrmEventsExtension.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\Nextras\Orm\Events\DI;
4

5
use Contributte\Nextras\Orm\Events\Listeners\AfterInsertListener;
6
use Contributte\Nextras\Orm\Events\Listeners\AfterPersistListener;
7
use Contributte\Nextras\Orm\Events\Listeners\AfterRemoveListener;
8
use Contributte\Nextras\Orm\Events\Listeners\AfterUpdateListener;
9
use Contributte\Nextras\Orm\Events\Listeners\BeforeInsertListener;
10
use Contributte\Nextras\Orm\Events\Listeners\BeforePersistListener;
11
use Contributte\Nextras\Orm\Events\Listeners\BeforeRemoveListener;
12
use Contributte\Nextras\Orm\Events\Listeners\BeforeUpdateListener;
13
use Contributte\Nextras\Orm\Events\Listeners\FlushListener;
14
use Contributte\Utils\Annotations;
15
use Nette\DI\CompilerExtension;
16
use Nette\DI\Definitions\ServiceDefinition;
17
use Nette\DI\ServiceCreationException;
18
use Nextras\Orm\Repository\IRepository;
19
use ReflectionClass;
20

21
final class NextrasOrmEventsExtension extends CompilerExtension
22
{
23

24
        /** @var string[][] */
25
        private static array $annotations = [
26
                'Lifecycle' => [
27
                        'onBeforeInsert' => BeforeInsertListener::class,
28
                        'onBeforePersist' => BeforePersistListener::class,
29
                        'onBeforeRemove' => BeforeRemoveListener::class,
30
                        'onBeforeUpdate' => BeforeUpdateListener::class,
31
                        'onAfterInsert' => AfterInsertListener::class,
32
                        'onAfterPersist' => AfterPersistListener::class,
33
                        'onAfterRemove' => AfterRemoveListener::class,
34
                        'onAfterUpdate' => AfterUpdateListener::class,
35
                        'onFlush' => FlushListener::class,
36
                ],
37
                'BeforeInsert' => [
38
                        'onBeforeInsert' => BeforeInsertListener::class,
39
                ],
40
                'BeforePersist' => [
41
                        'onBeforePersist' => BeforePersistListener::class,
42
                ],
43
                'BeforeRemove' => [
44
                        'onBeforeRemove' => BeforeRemoveListener::class,
45
                ],
46
                'BeforeUpdate' => [
47
                        'onBeforeUpdate' => BeforeUpdateListener::class,
48
                ],
49
                'AfterInsert' => [
50
                        'onAfterInsert' => AfterInsertListener::class,
51
                ],
52
                'AfterPersist' => [
53
                        'onAfterPersist' => AfterPersistListener::class,
54
                ],
55
                'AfterRemove' => [
56
                        'onAfterRemove' => AfterRemoveListener::class,
57
                ],
58
                'AfterUpdate' => [
59
                        'onAfterUpdate' => AfterUpdateListener::class,
60
                ],
61
                'Flush' => [
62
                        'onFlush' => FlushListener::class,
63
                ],
64
        ];
65

66
        /**
67
         * Decorate services
68
         */
69
        public function beforeCompile(): void
70
        {
71
                // Find registered IRepositories and parse their entities
72
                $mapping = $this->loadEntityMapping();
1✔
73

74
                // Attach listeners
75
                $this->loadListeners($mapping);
1✔
76
        }
1✔
77

78
        /**
79
         * Load entity mapping
80
         *
81
         * @return string[]
82
         */
83
        private function loadEntityMapping(): array
84
        {
85
                $mapping = [];
1✔
86

87
                $builder = $this->getContainerBuilder();
1✔
88
                $repositories = $builder->findByType(IRepository::class);
1✔
89

90
                foreach ($repositories as $repository) {
1✔
91
                        assert($repository instanceof ServiceDefinition);
1✔
92

93
                        /** @var string $repositoryClass */
94
                        $repositoryClass = $repository->getEntity();
1✔
95

96
                        // Skip invalid repositoryClass name
97
                        if (!class_exists($repositoryClass)) {
1✔
98
                                throw new ServiceCreationException(sprintf("Repository class '%s' not found", $repositoryClass));
×
99
                        }
100

101
                        // Skip invalid subtype ob IRepository
102
                        if (!method_exists($repositoryClass, 'getEntityClassNames'))
1✔
103

104
                        continue;
×
105

106
                        // Append mapping [repository => [entity1, entity2, entityN]
107
                        foreach ($repositoryClass::getEntityClassNames() as $entity) {
1✔
108
                                $mapping[$entity] = $repositoryClass;
1✔
109
                        }
110
                }
111

112
                return $mapping;
1✔
113
        }
114

115
        /**
116
         * @param string[] $mapping
117
         */
118
        private function loadListeners(array $mapping): void
1✔
119
        {
120
                $builder = $this->getContainerBuilder();
1✔
121

122
                foreach ($mapping as $entity => $repository) {
1✔
123
                        // Test invalid class name
124
                        if (!class_exists($entity)) {
1✔
125
                                throw new ServiceCreationException(sprintf("Entity class '%s' not found", $entity));
×
126
                        }
127

128
                        $types = [$entity];
1✔
129
                        $uses = class_uses($entity);
1✔
130
                        if ($uses !== false) {
1✔
131
                                $types = $uses + [$entity];
1✔
132
                        }
133

134
                        /** @var class-string $type */
135
                        foreach ($types as $type) {
1✔
136
                                // Parse annotations from phpDoc
137
                                $rf = new ReflectionClass($type);
1✔
138

139
                                // Add entity/trait as dependency
140
                                $builder->addDependency($rf);
1✔
141

142
                                // Try all annotations
143
                                foreach (self::$annotations as $annotation => $events) {
1✔
144
                                        /** @var class-string|null $listener */
145
                                        $listener = Annotations::getAnnotation($rf, $annotation);
1✔
146
                                        if ($listener !== null) {
1✔
147
                                                $this->loadListenerByAnnotation($events, $repository, $listener);
1✔
148
                                        }
149
                                }
150
                        }
151
                }
152
        }
1✔
153

154
        /**
155
         * @param string[] $events
156
         * @param class-string $listener
157
         */
158
        private function loadListenerByAnnotation(array $events, string $repository, string $listener): void
1✔
159
        {
160
                $builder = $this->getContainerBuilder();
1✔
161

162
                // Skip if repository is not registered in DIC
163
                if (($rsn = $builder->getByType($repository)) === null) {
1✔
164
                        throw new ServiceCreationException(sprintf("Repository service '%s' not found", $repository));
×
165
                }
166

167
                // Skip if listener is not registered in DIC
168
                if (($lsn = $builder->getByType($listener)) === null) {
1✔
169
                        throw new ServiceCreationException(sprintf("Listener service '%s' not found", $listener));
×
170
                }
171

172
                // Get definitions
173
                $repositoryDef = $builder->getDefinition($rsn);
1✔
174
                assert($repositoryDef instanceof ServiceDefinition);
1✔
175
                $listenerDef = $builder->getDefinition($lsn);
1✔
176

177
                // Check implementation
178
                $rf = new ReflectionClass($listener);
1✔
179

180
                foreach ($events as $event => $interface) {
1✔
181
                        if ($rf->implementsInterface($interface) === false) {
1✔
182
                                throw new ServiceCreationException(sprintf("Object '%s' should implement '%s'", $listener, $interface));
1✔
183
                        }
184

185
                        $repositoryDef->addSetup('$service->?[] = function() {call_user_func_array([?, ?], func_get_args());}', [
1✔
186
                                $event,
1✔
187
                                $listenerDef,
1✔
188
                                $event,
1✔
189
                        ]);
190
                }
191
        }
1✔
192

193
}
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