Relations::_initRelationships()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 4
nop 0
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 4
rs 10
1
<?php
2
/**
3
 * This file is part of the Divergence package.
4
 *
5
 * (c) Henry Paradiz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Divergence\Models;
12
13
use Exception;
14
15
/**
16
 * Relations.
17
 *
18
 * @package Divergence
19
 * @author  Henry Paradiz <[email protected]>
20
 *
21
 * @property array $_classRelationships
22
 * @property array $_classFields
23
 * @property string $rootClass
24
 * @property array $contextClasses
25
 */
26
trait Relations
27
{
28
    protected $_relatedObjects = [];
29
30 11
    public static function _relationshipExists($relationship)
31
    {
32 11
        if (is_array(static::$_classRelationships[get_called_class()])) {
33 11
            return array_key_exists($relationship, static::$_classRelationships[get_called_class()]);
34
        } else {
35 1
            return false;
36
        }
37
    }
38
39
    /**
40
     * Called when anything relationships related is used for the first time to define relationships before _initRelationships
41
     */
42 4
    protected static function _defineRelationships()
43
    {
44 4
        $className = get_called_class();
45 4
        $classes = class_parents($className);
46 4
        array_unshift($classes, $className);
47 4
        static::$_classRelationships[$className] = [];
48 4
        while ($class = array_pop($classes)) {
49 4
            if (!empty($class::$relationships)) {
50 4
                static::$_classRelationships[$className] = array_merge(static::$_classRelationships[$className], $class::$relationships);
51
            }
52 4
            if (static::isVersioned() && !empty($class::$versioningRelationships)) {
53 2
                static::$_classRelationships[$className] = array_merge(static::$_classRelationships[$className], $class::$versioningRelationships);
54
            }
55
        }
56
    }
57
58
59
    /**
60
     * Called after _defineRelationships to initialize and apply defaults to the relationships property
61
     * Must be idempotent as it may be applied multiple times up the inheritence chain
62
     */
63 4
    protected static function _initRelationships()
64
    {
65 4
        $className = get_called_class();
66 4
        if (!empty(static::$_classRelationships[$className])) {
67 4
            $relationships = [];
68 4
            foreach (static::$_classRelationships[$className] as $relationship => $options) {
69 4
                if (is_array($options)) {
70 4
                    $relationships[$relationship] = static::_initRelationship($relationship, $options);
71
                }
72
            }
73 4
            static::$_classRelationships[$className] = $relationships;
74
        }
75
    }
76
77 3
    protected static function _prepareOneOne(string $relationship, array $options): array
78
    {
79 3
        $options['local'] = $options['local'] ?? $relationship . 'ID';
80 3
        $options['foreign'] = $options['foreign'] ?? 'ID';
81 3
        return $options;
82
    }
83
84 3
    protected static function _prepareOneMany(string $classShortName, array $options): array
85
    {
86 3
        $options['local'] = $options['local'] ?? 'ID';
87 3
        $options['foreign'] = $options['foreign'] ?? $classShortName. 'ID';
88 3
        $options['indexField'] = $options['indexField'] ?? false;
89 3
        $options['conditions'] = $options['conditions'] ?? [];
90 3
        $options['conditions'] = is_string($options['conditions']) ? [$options['conditions']] : $options['conditions'];
91 3
        $options['order'] = $options['order'] ?? false;
92 3
        return $options;
93
    }
94
95 2
    protected static function _prepareContextChildren($options): array
96
    {
97 2
        $options['local'] = $options['local'] ?? 'ID';
98 2
        $options['contextClass'] = $options['contextClass'] ?? get_called_class();
99 2
        $options['indexField'] = $options['indexField'] ?? false;
100 2
        $options['conditions'] = $options['conditions'] ?? [];
101 2
        $options['order'] = $options['order'] ?? false;
102 2
        return $options;
103
    }
104
105 2
    protected static function _prepareContextParent($options): array
106
    {
107 2
        $options['local'] = $options['local'] ?? 'ContextID';
108 2
        $options['foreign'] = $options['foreign'] ?? 'ID';
109 2
        $options['classField'] = $options['classField'] ?? 'ContextClass';
110 2
        $options['allowedClasses'] = $options['allowedClasses'] ?? (!empty(static::$contextClasses) ? static::$contextClasses : null);
111 2
        return $options;
112
    }
113
114 3
    protected static function _prepareManyMany($classShortName, $options): array
115
    {
116 3
        if (empty($options['class'])) {
117 1
            throw new Exception('Relationship type many-many option requires a class setting.');
118
        }
119
120 2
        if (empty($options['linkClass'])) {
121 1
            throw new Exception('Relationship type many-many option requires a linkClass setting.');
122
        }
123
124 1
        $options['linkLocal'] = $options['linkLocal'] ?? $classShortName . 'ID';
125 1
        $options['linkForeign'] = $options['linkForeign'] ?? basename(str_replace('\\', '/', $options['class']::$rootClass)).'ID';
126 1
        $options['local'] = $options['local'] ?? 'ID';
127 1
        $options['foreign'] = $options['foreign'] ?? 'ID';
128 1
        $options['indexField'] = $options['indexField'] ?? false;
129 1
        $options['conditions'] = $options['conditions'] ?? [];
130 1
        $options['order'] = $options['order'] ?? false;
131 1
        return $options;
132
    }
133
134
    // TODO: Make relations getPrimaryKeyValue() instead of using ID all the time.
135 8
    protected static function _initRelationship($relationship, $options)
136
    {
137 8
        $classShortName = basename(str_replace('\\', '/', static::$rootClass));
138
139
        // apply defaults
140 8
        if (empty($options['type'])) {
141 3
            $options['type'] = 'one-one';
142
        }
143
144 8
        switch ($options['type']) {
145 8
            case 'one-one':
146 3
                $options = static::_prepareOneOne($relationship, $options);
147 3
                break;
148 7
            case 'one-many':
149 3
                $options = static::_prepareOneMany($classShortName, $options);
150 3
                break;
151 7
            case 'context-children':
152 2
                $options = static::_prepareContextChildren($options);
153 2
                break;
154 6
            case 'context-parent':
155 2
                $options = static::_prepareContextParent($options);
156 2
                break;
157 5
            case 'many-many':
158 3
                $options = static::_prepareManyMany($classShortName, $options);
159 1
                break;
160
        }
161
162 6
        if (static::isVersioned() && $options['type'] == 'history') {
163 2
            if (empty($options['class'])) {
164 2
                $options['class'] = get_called_class();
165
            }
166
        }
167
168 6
        return $options;
169
    }
170
171
    /**
172
     * Retrieves given relationship's value
173
     * @param string $relationship Name of relationship
174
     * @return mixed value
175
     */
176 10
    protected function _getRelationshipValue($relationship)
177
    {
178 10
        if (!isset($this->_relatedObjects[$relationship])) {
179 10
            $rel = static::$_classRelationships[get_called_class()][$relationship];
180
181 10
            if ($rel['type'] == 'one-one') {
182 2
                if ($value = $this->_getFieldValue($rel['local'])) {
183 2
                    $this->_relatedObjects[$relationship] = $rel['class']::getByField($rel['foreign'], $value);
184
185
                    // hook relationship for invalidation
186 2
                    static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
187
                } else {
188 2
                    $this->_relatedObjects[$relationship] = null;
189
                }
190 8
            } elseif ($rel['type'] == 'one-many') {
191 3
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
192
                    $rel['indexField'] = false;
193
                }
194
195 3
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByWhere(
196 3
                    array_merge($rel['conditions'], [
197 3
                        $rel['foreign'] => $this->_getFieldValue($rel['local']),
198 3
                    ]),
199 3
                    [
200 3
                        'indexField' => $rel['indexField'],
201 3
                        'order' => $rel['order'],
202 3
                        'conditions' => $rel['conditions'],
203 3
                    ]
204 3
                );
205
206
207
                // hook relationship for invalidation
208 3
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
209 5
            } elseif ($rel['type'] == 'context-children') {
210 1
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
211
                    $rel['indexField'] = false;
212
                }
213
214 1
                $conditions = array_merge($rel['conditions'], [
215 1
                    'ContextClass' => $rel['contextClass'],
216 1
                    'ContextID' => $this->_getFieldValue($rel['local']),
217 1
                ]);
218
219 1
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByWhere(
220 1
                    $conditions,
221 1
                    [
222 1
                        'indexField' => $rel['indexField'],
223 1
                        'order' => $rel['order'],
224 1
                    ]
225 1
                );
226
227
                // hook relationship for invalidation
228 1
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
229 4
            } elseif ($rel['type'] == 'context-parent') {
230 1
                $className = $this->_getFieldValue($rel['classField']);
231 1
                $this->_relatedObjects[$relationship] = $className ? $className::getByID($this->_getFieldValue($rel['local'])) : null;
232
233
                // hook both relationships for invalidation
234 1
                static::$_classFields[get_called_class()][$rel['classField']]['relationships'][$relationship] = true;
235 1
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
236 3
            } elseif ($rel['type'] == 'many-many') {
237
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
238
                    $rel['indexField'] = false;
239
                }
240
241
                // TODO: support indexField, conditions, and order
242
243
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByQuery(
244
                    'SELECT Related.* FROM `%s` Link JOIN `%s` Related ON (Related.`%s` = Link.%s) WHERE Link.`%s` = %u AND %s',
245
                    [
246
                        $rel['linkClass']::$tableName,
247
                        $rel['class']::$tableName,
248
                        $rel['foreign'],
249
                        $rel['linkForeign'],
250
                        $rel['linkLocal'],
251
                        $this->_getFieldValue($rel['local']),
252
                        $rel['conditions'] ? join(' AND ', $rel['conditions']) : '1',
253
                    ]
254
                );
255
256
                // hook relationship for invalidation
257
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
258 3
            } elseif ($rel['type'] == 'history' && static::isVersioned()) {
259 2
                $this->_relatedObjects[$relationship] = $rel['class']::getRevisionsByID($this->getPrimaryKeyValue(), $rel);
260
            }
261
        }
262
263 10
        return $this->_relatedObjects[$relationship];
264
    }
265
}
266