Completed
Branch feature/pre-split (f1ffcf)
by Anton
03:59
created

ORM::schemaBuilder()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 19
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 2
nop 1
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ORM;
8
9
use Interop\Container\ContainerInterface;
10
use Spiral\Core\Component;
11
use Spiral\Core\Container;
12
use Spiral\Core\Container\SingletonInterface;
13
use Spiral\Core\FactoryInterface;
14
use Spiral\Core\MemoryInterface;
15
use Spiral\Core\NullMemory;
16
use Spiral\Database\DatabaseManager;
17
use Spiral\Models\ActiveEntityInterface;
18
use Spiral\ORM\Exceptions\ORMException;
19
use Spiral\ORM\Exceptions\SchemaException;
20
use Spiral\ORM\Schemas\LocatorInterface;
21
use Spiral\ORM\Schemas\NullLocator;
22
use Spiral\ORM\Schemas\SchemaBuilder;
23
use Spiral\ORM\Schemas\SchemaLocator;
24
25
class ORM extends Component implements ORMInterface, SingletonInterface
26
{
27
    /**
28
     * Memory section to store ORM schema.
29
     */
30
    const MEMORY = 'orm.schema';
31
32
    /**
33
     * Already created instantiators.
34
     *
35
     * @invisible
36
     * @var InstantiatorInterface[]
37
     */
38
    private $instantiators = [];
39
40
    /**
41
     * ORM schema.
42
     *
43
     * @invisible
44
     * @var array
45
     */
46
    private $schema = [];
47
48
    /**
49
     * @var DatabaseManager
50
     */
51
    protected $manager;
52
53
    /**
54
     * @var SchemaLocator
55
     */
56
    protected $locator;
57
58
    /**
59
     * @invisible
60
     * @var MemoryInterface
61
     */
62
    protected $memory;
63
64
    /**
65
     * Container defines working scope for all Documents and DocumentEntities.
66
     *
67
     * @var ContainerInterface
68
     */
69
    protected $container;
70
71
    /**
72
     * @param DatabaseManager    $manager
73
     * @param LocatorInterface   $locator
74
     * @param MemoryInterface    $memory
75
     * @param ContainerInterface $container
76
     */
77
    public function __construct(
78
        DatabaseManager $manager,
79
        LocatorInterface $locator = null,
80
        MemoryInterface $memory = null,
81
        ContainerInterface $container = null
82
    ) {
83
        $this->manager = $manager;
84
85
        $this->locator = $locator ?? new NullLocator();
0 ignored issues
show
Documentation Bug introduced by
$locator ?? new \Spiral\ORM\Schemas\NullLocator() is of type object<Spiral\ORM\Schemas\LocatorInterface>, but the property $locator was declared to be of type object<Spiral\ORM\Schemas\SchemaLocator>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
86
        $this->memory = $memory ?? new NullMemory();
87
        $this->container = $container ?? new Container();
88
89
        //Loading schema from memory (if any)
90
        $this->schema = $this->loadSchema();
91
    }
92
93
    /**
94
     * Create instance of ORM SchemaBuilder.
95
     *
96
     * @param bool $locate Set to true to automatically locate available records and record sources
97
     *                     sources in a project files (based on tokenizer scope).
98
     *
99
     * @return SchemaBuilder
100
     *
101
     * @throws SchemaException
102
     */
103
    public function schemaBuilder(bool $locate = true): SchemaBuilder
104
    {
105
        /**
106
         * @var SchemaBuilder $builder
107
         */
108
        $builder = $this->getFactory()->make(SchemaBuilder::class, ['manager' => $this->manager]);
109
110
        if ($locate) {
111
            foreach ($this->locator->locateSchemas() as $schema) {
112
                $builder->addSchema($schema);
113
            }
114
115
            foreach ($this->locator->locateSources() as $class => $source) {
116
                $builder->addSource($class, $source);
117
            }
118
        }
119
120
        return $builder;
121
    }
122
123
    public function requireSync()
124
    {
125
126
    }
127
128
    /**
129
     * Specify behaviour schema for ORM to be used. Attention, you have to call renderSchema()
130
     * prior to passing builder into this method.
131
     *
132
     * @param SchemaBuilder $builder
133
     * @param bool          $remember Set to true to remember packed schema in memory.
134
     */
135
    public function buildSchema(SchemaBuilder $builder, bool $remember = false)
136
    {
137
        $this->schema = $builder->packSchema();
138
139
        if ($remember) {
140
            $this->memory->saveData(static::MEMORY, $this->schema);
141
        }
142
    }
143
144
    /**
145
     * {@inheritdoc}
146
     */
147
    public function define(string $class, int $property)
148
    {
149
        if (empty($this->schema)) {
150
            //Update and remember
151
            $this->buildSchema($this->schemaBuilder()->renderSchema(), true);
152
        }
153
154
        //Check value
155
        if (!isset($this->schema[$class])) {
156
            throw new ORMException("Undefined ORM schema item '{$class}', make sure schema is updated");
157
        }
158
159
        if (!array_key_exists($property, $this->schema[$class])) {
160
            throw new ORMException("Undefined ORM schema property '{$class}'.'{$property}'");
161
        }
162
163
        return $this->schema[$class][$property];
164
    }
165
166
    //other methods
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function make(
172
        string $class,
173
        $fields = [],
174
        bool $filter = true,
175
        bool $cache = false
176
    ): ActiveEntityInterface {
177
        //todo: cache
178
        return $this->instantiator($class)->make($fields, $filter);
179
    }
180
181
    //todo: __clone
182
183
    /**
184
     * Get object responsible for class instantiation.
185
     *
186
     * @param string $class
187
     *
188
     * @return InstantiatorInterface
189
     */
190
    protected function instantiator(string $class): InstantiatorInterface
191
    {
192
        if (isset($this->instantiators[$class])) {
193
            return $this->instantiators[$class];
194
        }
195
196
        //Potential optimization
197
        $instantiator = $this->getFactory()->make(
198
            $this->define($class, self::R_INSTANTIATOR),
199
            [
200
                'class'  => $class,
201
                'orm'    => $this,
202
                'schema' => $this->define($class, self::R_SCHEMA)
203
            ]
204
        );
205
206
        //Constructing instantiator and storing it in cache
207
        return $this->instantiators[$class] = $instantiator;
208
    }
209
210
    /**
211
     * Load packed schema from memory.
212
     *
213
     * @return array
214
     */
215
    protected function loadSchema(): array
216
    {
217
        return (array)$this->memory->loadData(static::MEMORY);
218
    }
219
220
    /**
221
     * Get ODM specific factory.
222
     *
223
     * @return FactoryInterface
224
     */
225
    protected function getFactory(): FactoryInterface
226
    {
227
        if ($this->container instanceof FactoryInterface) {
228
            return $this->container;
229
        }
230
231
        return $this->container->get(FactoryInterface::class);
232
    }
233
}