Completed
Branch feature/pre-split (548a16)
by Anton
03:55
created

ORM::getFactory()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 9.4285
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
It seems like $locator ?? new \Spiral\ORM\Schemas\NullLocator() of type object<Spiral\ORM\Schemas\LocatorInterface> is incompatible with the declared type object<Spiral\ORM\Schemas\SchemaLocator> of property $locator.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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
    /**
124
     * Specify behaviour schema for ORM to be used.
125
     *
126
     * @param SchemaBuilder $builder
127
     * @param bool          $remember Set to true to remember packed schema in memory.
128
     */
129
    public function setSchema(SchemaBuilder $builder, bool $remember = false)
130
    {
131
        $this->schema = $builder->packSchema();
132
133
        if ($remember) {
134
            $this->memory->saveData(static::MEMORY, $this->schema);
135
        }
136
    }
137
138
    /**
139
     * {@inheritdoc}
140
     */
141
    public function define(string $class, int $property)
142
    {
143
        if (empty($this->schema)) {
144
            //Update and remember
145
            $this->setSchema($this->schemaBuilder()->renderSchema(), true);
146
        }
147
148
        //Check value
149
        if (!isset($this->schema[$class])) {
150
            throw new ORMException("Undefined ORM schema item '{$class}', make sure schema is updated");
151
        }
152
153
        if (!array_key_exists($property, $this->schema[$class])) {
154
            throw new ORMException("Undefined ORM schema property '{$class}'.'{$property}'");
155
        }
156
157
        return $this->schema[$class][$property];
158
    }
159
160
    //other methods
161
162
    /**
163
     * {@inheritdoc}
164
     */
165
    public function make(
166
        string $class,
167
        $fields = [],
168
        bool $filter = true,
169
        bool $cache = false
170
    ): ActiveEntityInterface {
171
        //todo: cache
172
        return $this->instantiator($class)->make($fields, $filter);
173
    }
174
175
    //todo: __clone
176
177
    /**
178
     * Get object responsible for class instantiation.
179
     *
180
     * @param string $class
181
     *
182
     * @return InstantiatorInterface
183
     */
184
    protected function instantiator(string $class): InstantiatorInterface
185
    {
186
        if (isset($this->instantiators[$class])) {
187
            return $this->instantiators[$class];
188
        }
189
190
        //Potential optimization
191
        $instantiator = $this->getFactory()->make(
192
            $this->define($class, self::R_INSTANTIATOR),
193
            [
194
                'class'  => $class,
195
                'orm'    => $this,
196
                'schema' => $this->define($class, self::R_SCHEMA)
197
            ]
198
        );
199
200
        //Constructing instantiator and storing it in cache
201
        return $this->instantiators[$class] = $instantiator;
202
    }
203
204
    /**
205
     * Load packed schema from memory.
206
     *
207
     * @return array
208
     */
209
    protected function loadSchema(): array
210
    {
211
        return (array)$this->memory->loadData(static::MEMORY);
212
    }
213
214
    /**
215
     * Get ODM specific factory.
216
     *
217
     * @return FactoryInterface
218
     */
219
    protected function getFactory(): FactoryInterface
220
    {
221
        if ($this->container instanceof FactoryInterface) {
222
            return $this->container;
223
        }
224
225
        return $this->container->get(FactoryInterface::class);
226
    }
227
}