Schema::createRecordFromRow()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 2
dl 0
loc 17
ccs 9
cts 9
cp 1
crap 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Simply\Database;
4
5
use Psr\Container\ContainerInterface;
6
use Simply\Database\Exception\InvalidRelationshipException;
7
8
/**
9
 * A schema for a database table.
10
 * @author Riikka Kalliomäki <[email protected]>
11
 * @copyright Copyright (c) 2018 Riikka Kalliomäki
12
 * @license http://opensource.org/licenses/mit-license.php MIT License
13
 */
14
abstract class Schema
15
{
16
    /** @var Relationship[] Cached initialized relationships for the schema */
17
    private $relationshipCache;
18
19
    /** @var ContainerInterface Container used to load schemas for relationships */
20
    private $container;
21
22
    /**
23
     * Schema constructor.
24
     * @param ContainerInterface $container Container used to load schemas for relationships
25
     */
26 73
    public function __construct(ContainerInterface $container)
27
    {
28 73
        $this->relationshipCache = [];
29 73
        $this->container = $container;
30 73
    }
31
32
    /**
33
     * Returns the model class used to operate on the records from this schema.
34
     * @return string The model class used to operate on the records from this schema
35
     */
36
    abstract public function getModel(): string;
37
38
    /**
39
     * Returns the name of the table for the schema.
40
     * @return string Name of the table for the schema
41
     */
42
    abstract public function getTable(): string;
43
44
    /**
45
     * Returns a list fields that define the primary key for the schema.
46
     * @return string[] List fields that define the primary key for the schema
47
     */
48
    abstract public function getPrimaryKey(): array;
49
50
    /**
51
     * Returns the list of fields for the schema.
52
     * @return string[] List of fields for the schema
53
     */
54
    abstract public function getFields(): array;
55
56
    /**
57
     * Returns an associative list of relationship definitions for the schema.
58
     *
59
     * The returned array is an associative array of definitions, where each key defines the name of the relationship.
60
     *
61
     * Each definition must contain the following fields:
62
     * - key : Defines the field (or list of fields) in the schema that references fields in the referenced schema
63
     * - schema : Name of the schema in the container that the the relationship references
64
     * - field : Defines the field (or list of fields) in the referenced schema
65
     * - unique : If relationship can ever only refer to a single record (optional)
66
     *
67
     * If the unique value is missing or even if it's defined as `false`, the relationship will still be considered
68
     * unique if the referenced fields contain the entire primary key of the referenced schema (as it is not possible
69
     * to have multiple records with the same primary key).
70
     *
71
     * Note that each relationship must have an appropriate reverse relationship defined in the referenced schema.
72
     *
73
     * @return array[] Associative list of relationship definitions for the schema
74
     */
75
    abstract public function getRelationshipDefinitions(): array;
76
77
    /**
78
     * Returns a list of all relationships for the schema.
79
     * @return Relationship[] List of all relationships for the schema
80
     */
81 9
    public function getRelationships(): array
82
    {
83 9
        $relationships = [];
84
85 9
        foreach (array_keys($this->getRelationshipDefinitions()) as $name) {
86 9
            $relationships[] = $this->getRelationship($name);
87
        }
88
89 9
        return $relationships;
90
    }
91
92
    /**
93
     * Returns a single relationship with the given name.
94
     * @param string $name Name of the relationship
95
     * @return Relationship Relationship with the given name
96
     * @throws InvalidRelationshipException If a relationship with the given name does not exist
97
     */
98 35
    public function getRelationship(string $name): Relationship
99
    {
100 35
        if (isset($this->relationshipCache[$name])) {
101 13
            return $this->relationshipCache[$name];
102
        }
103
104 35
        $definition = $this->getRelationshipDefinitions()[$name] ?? null;
105
106 35
        if (empty($definition)) {
107 1
            throw new InvalidRelationshipException("Undefined relationship '$name'");
108
        }
109
110 34
        $key = \is_array($definition['key']) ? $definition['key'] : [$definition['key']];
111 34
        $schema = $this->loadSchema($definition['schema']);
112 34
        $fields = \is_array($definition['field']) ? $definition['field'] : [$definition['field']];
113 34
        $unique = empty($definition['unique']) ? false : true;
114
115 34
        $this->relationshipCache[$name] = new Relationship($name, $this, $key, $schema, $fields, $unique);
116
117 34
        return $this->relationshipCache[$name];
118
    }
119
120
    /**
121
     * Loads a schema with the given name from the container.
122
     * @param string $name Name of the schema
123
     * @return Schema The schema loaded from the container
124
     */
125 34
    private function loadSchema(string $name): self
126
    {
127 34
        return $this->container->get($name);
128
    }
129
130
    /**
131
     * Returns a new empty record for the schema.
132
     * @param Model|null $model A model to associate to the record, if initialized
133
     * @return Record A new empty record based on this schema
134
     */
135 47
    public function createRecord(Model $model = null): Record
136
    {
137 47
        return new Record($this, $model);
138
    }
139
140
    /**
141
     * Returns a new record with the given values for the fields.
142
     * @param array $values Values for the record fields
143
     * @return Record New record with given values
144
     */
145 16
    public function createRecordFromValues(array $values): Record
146
    {
147 16
        $record = $this->createRecord();
148 16
        $record->setDatabaseValues($values);
149
150 16
        return $record;
151
    }
152
153
    /**
154
     * Returns a new record with values taken from a result row with optional prefix for fields.
155
     * @param array $row The result row from a database query
156
     * @param string $prefix Prefix for the fields used by this schema
157
     * @return Record New record based on the fields taken from the result row
158
     */
159 16
    public function createRecordFromRow(array $row, string $prefix = ''): Record
160
    {
161 16
        if ($prefix === '') {
162 11
            return $this->createRecordFromValues(array_intersect_key($row, array_flip($this->getFields())));
163
        }
164
165 5
        $values = [];
166
167 5
        foreach ($this->getFields() as $field) {
168 5
            $prefixed = $prefix . $field;
169
170 5
            if (array_key_exists($prefixed, $row)) {
171 5
                $values[$field] = $row[$prefixed];
172
            }
173
        }
174
175 5
        return $this->createRecordFromValues($values);
176
    }
177
178
    /**
179
     * Returns a new model that is associated to the given record.
180
     * @param Record $record The record associated to the model
181
     * @return Model New model based on the given record
182
     */
183 21
    public function createModel(Record $record): Model
184
    {
185 21
        if ($record->getSchema() !== $this) {
186 1
            throw new \InvalidArgumentException('The provided record must have a matching schema');
187
        }
188
189
        /** @var Model $model */
190 20
        $model = $this->getModel();
191
192 20
        return $model::createFromDatabaseRecord($record);
193
    }
194
195
    /**
196
     * Returns a new model with a new record that has values from the given result row with optional relationships.
197
     * @param array $row Result row from the database
198
     * @param string $prefix Prefix for the fields of this schema
199
     * @param array $relationships Names of unique relationships also provided in the result row
200
     * @return Model New model based on the values provided in the result row
201
     */
202 15
    public function createModelFromRow(array $row, string $prefix = '', array $relationships = []): Model
203
    {
204 15
        $record = $this->createRecordFromRow($row, $prefix);
205
206 15
        foreach ($relationships as $key => $name) {
207 3
            $relationship = $this->getRelationship($name);
208 3
            $schema = $relationship->getReferencedSchema();
209 3
            $relationship->fillSingleRecord($record, $schema->createRecordFromRow($row, $key));
210
        }
211
212 13
        return $record->getModel();
213
    }
214
}
215