Passed
Push — master ( c93c78...d9ab06 )
by Anton
04:33 queued 02:57
created

src/Entities.php (1 issue)

Checks if the types of the passed arguments in a function/method call are compatible.

Bug Minor
1
<?php declare(strict_types=1);
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Cycle\Annotated;
10
11
use Cycle\Annotated\Annotation\Entity;
12
use Cycle\Schema\Definition\Entity as EntitySchema;
13
use Cycle\Schema\Exception\RegistryException;
14
use Cycle\Schema\Exception\RelationException;
15
use Cycle\Schema\GeneratorInterface;
16
use Cycle\Schema\Registry;
17
use Doctrine\Common\Inflector\Inflector;
18
use Spiral\Annotations\Parser;
19
use Spiral\Tokenizer\ClassesInterface;
20
21
/**
22
 * Generates ORM schema based on annotated classes.
23
 */
24
final class Entities implements GeneratorInterface
25
{
26
    /** @var ClassesInterface */
27
    private $locator;
28
29
    /** @var Parser */
30
    private $parser;
31
32
    /** @var Generator */
33
    private $generator;
34
35
    /**
36
     * @param ClassesInterface $locator
37
     * @param Parser|null      $parser
38
     */
39
    public function __construct(ClassesInterface $locator, Parser $parser = null)
40
    {
41
        $this->locator = $locator;
42
        $this->parser = $parser ?? Generator::getDefaultParser();
43
        $this->generator = new Generator($this->parser);
44
    }
45
46
    /**
47
     * @param Registry $registry
48
     * @return Registry
49
     */
50
    public function run(Registry $registry): Registry
51
    {
52
        /** @var EntitySchema[] $children */
53
        $children = [];
54
        foreach ($this->locator->getClasses() as $class) {
55
            if ($class->getDocComment() === false) {
56
                continue;
57
            }
58
59
            $ann = $this->parser->parse($class->getDocComment());
60
            if (!isset($ann[Entity::NAME])) {
61
                continue;
62
            }
63
64
            /** @var Entity $ea */
65
            $ea = $ann[Entity::NAME];
66
67
            $e = $this->generator->initEntity($ea, $class);
68
69
            // columns
70
            $this->generator->initFields($e, $class);
71
72
            // relations
73
            $this->generator->initRelations($e, $class);
74
75
            // additional columns (mapped to local fields automatically)
76
            $this->generator->initColumns($e, $ea->getColumns(), $class);
77
78
            if ($this->hasParent($registry, $e->getClass())) {
79
                $children[] = $e;
80
                continue;
81
            }
82
83
            // register entity (OR find parent)
84
            $registry->register($e);
85
86
            $registry->linkTable(
87
                $e,
88
                $ea->getDatabase(),
89
                $ea->getTable() ?? $this->tableName($e->getRole())
90
            );
91
        }
92
93
        foreach ($children as $e) {
94
            $registry->registerChild($registry->getEntity($this->findParent($registry, $e->getClass())), $e);
95
        }
96
97
        return $this->normalizeNames($registry);
98
    }
99
100
    /**
101
     * @param Registry $registry
102
     * @return Registry
103
     */
104
    protected function normalizeNames(Registry $registry): Registry
105
    {
106
        // resolve all the relation target names into roles
107
        foreach ($this->locator->getClasses() as $class) {
108
            if (!$registry->hasEntity($class->getName())) {
109
                continue;
110
            }
111
112
            $e = $registry->getEntity($class->getName());
113
114
            // relations
115
            foreach ($e->getRelations() as $name => $r) {
116
                try {
117
                    $r->setTarget($this->resolveTarget($registry, $r->getTarget()));
118
119
                    if ($r->getOptions()->has('though')) {
120
                        $r->getOptions()->set(
121
                            'though',
122
                            $this->resolveTarget($registry, $r->getOptions()->get('though'))
123
                        );
124
                    }
125
                } catch (RegistryException $ex) {
126
                    throw new RelationException(
127
                        sprintf("Unable to resolve `%s`.`%s` relation target (not found or invalid)",
128
                            $e->getRole(),
129
                            $name
130
                        ),
131
                        $ex->getCode(),
132
                        $ex
133
                    );
134
                }
135
            }
136
        }
137
138
        return $registry;
139
    }
140
141
    /**
142
     * @param Registry $registry
143
     * @param string   $name
144
     * @return string|null
145
     */
146
    protected function resolveTarget(Registry $registry, string $name): ?string
147
    {
148
        if (is_null($name) || interface_exists($name, true)) {
149
            // do not resolve interfaces
150
            return $name;
151
        }
152
153
        return $registry->getEntity($name)->getRole();
154
    }
155
156
    /**
157
     * @param string $role
158
     * @return string
159
     */
160
    protected function tableName(string $role): string
161
    {
162
        return Inflector::pluralize(Inflector::tableize($role));
163
    }
164
165
    /**
166
     * @param Registry $registry
167
     * @param string   $class
168
     * @return bool
169
     */
170
    protected function hasParent(Registry $registry, string $class): bool
171
    {
172
        return $this->findParent($registry, $class) !== null;
173
    }
174
175
    /**
176
     * @param Registry $registry
177
     * @param string   $class
178
     * @return string|null
179
     */
180
    protected function findParent(Registry $registry, string $class): ?string
181
    {
182
        $parents = class_parents($class);
183
        foreach (array_reverse($parents) as $parent) {
184
            $class = new \ReflectionClass($parent);
185
            if ($class->getDocComment() === false) {
186
                continue;
187
            }
188
189
            $ann = $this->parser->parse($class->getDocComment());
0 ignored issues
show
It seems like $class->getDocComment() can also be of type true; however, parameter $body of Spiral\Annotations\Parser::parse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

189
            $ann = $this->parser->parse(/** @scrutinizer ignore-type */ $class->getDocComment());
Loading history...
190
            if (isset($ann[Entity::NAME])) {
191
                return $parent;
192
            }
193
        }
194
195
        return null;
196
    }
197
}