Passed
Branch release (bd7374)
by Henry
03:48
created

Relations::_prepareContextParent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
ccs 6
cts 6
cp 1
crap 2
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 15
    public static function _relationshipExists($relationship)
31
    {
32 15
        if (is_array(static::$_classRelationships[get_called_class()])) {
33 15
            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 2
        $options['local'] = $options['local'] ?? 'ID';
97 2
        $options['contextClass'] = $options['contextClass'] ?? get_called_class();
98 2
        $options['indexField'] = $options['indexField'] ?? false;
99 2
        $options['conditions'] = $options['conditions'] ?? [];
100 2
        $options['order'] = $options['order'] ?? false;
101 2
        return $options;
102
    }
103
104 2
    protected static function _prepareContextParent($options): array {
105 2
        $options['local'] = $options['local'] ?? 'ContextID';
106 2
        $options['foreign'] = $options['foreign'] ?? 'ID';
107 2
        $options['classField'] = $options['classField'] ?? 'ContextClass';
108 2
        $options['allowedClasses'] = $options['allowedClasses'] ?? (!empty(static::$contextClasses)?static::$contextClasses:null);
109 2
        return $options;
110
    }
111
112 3
    protected static function _prepareManyMany($classShortName, $options): array {
113 3
        if (empty($options['class'])) {
114 1
            throw new Exception('Relationship type many-many option requires a class setting.');
115
        }
116
117 2
        if (empty($options['linkClass'])) {
118 1
            throw new Exception('Relationship type many-many option requires a linkClass setting.');
119
        }
120
121 1
        $options['linkLocal'] = $options['linkLocal'] ?? $classShortName . 'ID';
122 1
        $options['linkForeign'] = $options['linkForeign'] ?? basename(str_replace('\\', '/', $options['class']::$rootClass)).'ID';
123 1
        $options['local'] = $options['local'] ?? 'ID';
124 1
        $options['foreign'] = $options['foreign'] ?? 'ID';
125 1
        $options['indexField'] = $options['indexField'] ?? false;
126 1
        $options['conditions'] = $options['conditions'] ?? [];
127 1
        $options['order'] = $options['order'] ?? false;
128 1
        return $options;
129
    }
130
131
    // TODO: Make relations getPrimaryKeyValue() instead of using ID all the time.
132 8
    protected static function _initRelationship($relationship, $options)
133
    {
134 8
        $classShortName = basename(str_replace('\\', '/', static::$rootClass));
135
136
        // apply defaults
137 8
        if (empty($options['type'])) {
138 3
            $options['type'] = 'one-one';
139
        }
140
141 8
        switch($options['type']) {
142 8
            case 'one-one':
143 3
                $options = static::_prepareOneOne($relationship, $options);
144 3
                break;
145 7
            case 'one-many':
146 3
                $options = static::_prepareOneMany($classShortName, $options);
147 3
                break;
148 7
            case 'context-children':
149 2
                $options = static::_prepareContextChildren($options);
150 2
                break;
151 6
            case 'context-parent':
152 2
                $options = static::_prepareContextParent($options);
153 2
                break;
154 5
            case 'many-many':
155 3
                $options = static::_prepareManyMany($classShortName,$options);
156 1
                break;
157
        }
158
159 6
        if (static::isVersioned() && $options['type'] == 'history') {
160 2
            if (empty($options['class'])) {
161 2
                $options['class'] = get_called_class();
162
            }
163
        }
164
165 6
        return $options;
166
    }
167
168
    /**
169
     * Retrieves given relationship's value
170
     * @param string $relationship Name of relationship
171
     * @return mixed value
172
     */
173 10
    protected function _getRelationshipValue($relationship)
174
    {
175 10
        if (!isset($this->_relatedObjects[$relationship])) {
176 10
            $rel = static::$_classRelationships[get_called_class()][$relationship];
177
178 10
            if ($rel['type'] == 'one-one') {
179 2
                if ($value = $this->_getFieldValue($rel['local'])) {
180 2
                    $this->_relatedObjects[$relationship] = $rel['class']::getByField($rel['foreign'], $value);
181
182
                    // hook relationship for invalidation
183 2
                    static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
184
                } else {
185 2
                    $this->_relatedObjects[$relationship] = null;
186
                }
187 8
            } elseif ($rel['type'] == 'one-many') {
188 3
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
189
                    $rel['indexField'] = false;
190
                }
191
192 3
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByWhere(
193 3
                    array_merge($rel['conditions'], [
194 3
                        $rel['foreign'] => $this->_getFieldValue($rel['local']),
195 3
                    ]),
196 3
                    [
197 3
                        'indexField' => $rel['indexField'],
198 3
                        'order' => $rel['order'],
199 3
                        'conditions' => $rel['conditions'],
200 3
                    ]
201 3
                );
202
203
204
                // hook relationship for invalidation
205 3
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
206 5
            } elseif ($rel['type'] == 'context-children') {
207 1
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
208
                    $rel['indexField'] = false;
209
                }
210
211 1
                $conditions = array_merge($rel['conditions'], [
212 1
                    'ContextClass' => $rel['contextClass'],
213 1
                    'ContextID' => $this->_getFieldValue($rel['local']),
214 1
                ]);
215
216 1
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByWhere(
217 1
                    $conditions,
218 1
                    [
219 1
                        'indexField' => $rel['indexField'],
220 1
                        'order' => $rel['order'],
221 1
                    ]
222 1
                );
223
224
                // hook relationship for invalidation
225 1
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
226 4
            } elseif ($rel['type'] == 'context-parent') {
227 1
                $className = $this->_getFieldValue($rel['classField']);
228 1
                $this->_relatedObjects[$relationship] = $className ? $className::getByID($this->_getFieldValue($rel['local'])) : null;
229
230
                // hook both relationships for invalidation
231 1
                static::$_classFields[get_called_class()][$rel['classField']]['relationships'][$relationship] = true;
232 1
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
233 3
            } elseif ($rel['type'] == 'many-many') {
234
                if (!empty($rel['indexField']) && !$rel['class']::fieldExists($rel['indexField'])) {
235
                    $rel['indexField'] = false;
236
                }
237
238
                // TODO: support indexField, conditions, and order
239
240
                $this->_relatedObjects[$relationship] = $rel['class']::getAllByQuery(
241
                    'SELECT Related.* FROM `%s` Link JOIN `%s` Related ON (Related.`%s` = Link.%s) WHERE Link.`%s` = %u AND %s',
242
                    [
243
                        $rel['linkClass']::$tableName,
244
                        $rel['class']::$tableName,
245
                        $rel['foreign'],
246
                        $rel['linkForeign'],
247
                        $rel['linkLocal'],
248
                        $this->_getFieldValue($rel['local']),
249
                        $rel['conditions'] ? join(' AND ', $rel['conditions']) : '1',
250
                    ]
251
                );
252
253
                // hook relationship for invalidation
254
                static::$_classFields[get_called_class()][$rel['local']]['relationships'][$relationship] = true;
255 3
            } elseif ($rel['type'] == 'history' && static::isVersioned()) {
256 2
                $this->_relatedObjects[$relationship] = $rel['class']::getRevisionsByID($this->getPrimaryKeyValue(), $rel);
257
            }
258
        }
259
260 10
        return $this->_relatedObjects[$relationship];
261
    }
262
}
263