OrmMapping::mapReferenced()   B
last analyzed

Complexity

Conditions 5
Paths 19

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.1158

Importance

Changes 0
Metric Value
dl 0
loc 32
ccs 15
cts 18
cp 0.8333
rs 8.439
c 0
b 0
f 0
cc 5
eloc 18
nc 19
nop 3
crap 5.1158
1
<?php
2
namespace Nkey\Caribu\Orm;
3
4
use Nkey\Caribu\Model\AbstractModel;
5
use PDOException;
6
7
trait OrmMapping
8
{
9
    use OrmAnnotation;
10
11
    /**
12
     * Map a object from default class into specific
13
     *
14
     * @param \stdClass $from
15
     *            The unmapped data as stdClass object
16
     * @param string $toClass
17
     *            The name of class to map data into
18
     *            
19
     * @return object The new created object of $toClass containing the mapped data
20
     *        
21
     * @throws OrmException
22
     * @throws PDOException
23
     */
24 26
    private static function map(\stdClass $from, string $toClass, Orm $orm)
25
    {
26 26
        $result = self::mapAnnotated($from, $toClass);
27
        
28 26
        self::mapReferenced($from, $toClass, $result);
29 26
        if (self::isEager($toClass)) {
30 5
            self::injectMappedBy($toClass, $result, $orm);
31
        }
32
        
33 25
        return $result;
34
    }
35
36
    /**
37
     * Map a referenced object into current mapped object
38
     *
39
     * @param object $from
40
     *            The unmapped object as stdClass
41
     *            
42
     * @param string $toClass
43
     *            The name of class where the mapped data will be stored into
44
     *            
45
     * @param AbstractModel $result
46
     *            The mapped entity
47
     */
48 26
    private static function mapReferenced(\stdClass $from, string $toClass, AbstractModel $result)
49
    {
50
        try {
51 26
            $rfToClass = new \ReflectionClass($toClass);
52
            
53 26
            foreach (get_object_vars($from) as $property => $value) {
54 26
                if (! strpos($property, '.')) {
55 26
                    continue;
56
                }
57
                
58 3
                list ($toProperty, $column) = explode('.', $property);
59
                
60 3
                if (! $rfToClass->hasProperty($toProperty)) {
61
                    continue;
62
                }
63
                
64 3
                $referencedClass = self::getAnnotatedPropertyType($toClass, $toProperty, $rfToClass->getNamespaceName());
65 3
                $rfReferenced = new \ReflectionClass($referencedClass);
66
                
67 3
                $findMethod = $rfReferenced->getMethod("find");
68 3
                $referencedObject = $findMethod->invoke(null, array(
69 3
                    $column => $value
70
                ));
71
                
72 3
                $propertySetter = $rfToClass->getMethod(sprintf("set%s", ucfirst($toProperty)));
73
                
74 26
                $propertySetter->invoke($result, $referencedObject);
75
            }
76
        } catch (\ReflectionException $exception) {
77
            throw OrmException::fromPrevious($exception);
78
        }
79 26
    }
80
81
    /**
82
     * Inject the mappedBy annotated properties
83
     *
84
     * @param string $toClass
85
     *            The class of entity
86
     *            
87
     * @param AbstractModel $object
88
     *            Prefilled entity
89
     *            
90
     * @param Orm $orm
91
     *            The Orm instance
92
     *            
93
     * @throws OrmException
94
     * @throws PDOException
95
     */
96 5
    private static function injectMappedBy(string $toClass, AbstractModel &$object, Orm $orm)
97
    {
98
        try {
99 5
            $rfToClass = new \ReflectionClass($toClass);
100
            
101 5
            foreach ($rfToClass->getProperties() as $property) {
102 5
                if ("" === ($parameters = self::getAnnotatedMappedByParameters($property->getDocComment()))) {
103 5
                    continue;
104
                }
105
                
106 5
                $mappedBy = self::parseMappedBy($parameters);
107
                
108 5
                $type = self::getAnnotatedType($property->getDocComment(), $rfToClass->getNamespaceName());
109
                
110 4
                if ("" === $type) {
111
                    throw new OrmException("Can't use mappedBy without specific type for property {property}", array(
112
                        'property' => $property->getName()
113
                    ));
114
                }
115
                
116 4
                if (self::isPrimitive($type)) {
117
                    throw new OrmException("Primitive type can not be used in mappedBy for property {property}", array(
118
                        'property' => $property->getName()
119
                    ));
120
                }
121
                
122 4
                $getMethod = new \ReflectionMethod($toClass, sprintf("get%s", ucfirst($property->getName())));
123 4
                if ($getMethod->invoke($object)) {
124 2
                    continue;
125
                }
126
                
127 2
                $ownPrimaryKey = self::getPrimaryKey($toClass, $object, true);
128
                
129 2
                $otherTable = self::getTableName($type);
130 2
                $otherPrimaryKeyName = self::getPrimaryKeyCol($type);
131 2
                $ownPrimaryKeyName = self::getPrimaryKeyCol($toClass);
132
                
133 2
                $query = sprintf("SELECT %s.* FROM %s
134
                        JOIN %s ON %s.%s = %s.%s
135 2
                        WHERE %s.%s = :%s", $otherTable, $otherTable, $mappedBy['table'], $mappedBy['table'], $mappedBy['column'], $otherTable, $otherPrimaryKeyName, $mappedBy['table'], $mappedBy['inverseColumn'], $ownPrimaryKeyName);
136
                
137 2
                $statement = null;
138
                
139
                try {
140 2
                    $statement = $orm->startTX()->prepare($query);
141 2
                    $statement->bindValue(sprintf(":%s", $ownPrimaryKeyName), $ownPrimaryKey);
142
                    
143 2
                    $statement->execute();
144
                    
145 2
                    $result = $statement->fetch(\PDO::FETCH_OBJ);
146
                    
147 2
                    if (false == $result) {
148
                        throw new OrmException("No foreign entity found for {entity} using primary key {pk}", array(
149
                            'entity' => $toClass,
150
                            'pk' => $$ownPrimaryKey
151
                        ));
152
                    }
153
                    
154 2
                    $orm->commitTX();
155
                    
156 2
                    $setMethod = new \ReflectionMethod($toClass, sprintf("set%s", ucfirst($property->getName())));
157
                    
158 2
                    $setMethod->invoke($object, self::map($result, $type, $orm));
159
                } catch (\PDOException $exception) {
160 4
                    throw self::handleException($orm, $statement, $exception, "Mapping failed", - 1010);
161
                }
162
            }
163 1
        } catch (\ReflectionException $exception) {
164
            throw OrmException::fromPrevious($exception);
165
        }
166 4
    }
167
168
    /**
169
     * Map default class object into specific by annotation
170
     *
171
     * @param object $from
172
     *            The unmapped dataset
173
     * @param string $toClass
174
     *            The name of class where to map data in
175
     *            
176
     * @return AbstractModel The mapped data as entity
177
     *        
178
     * @throws OrmException
179
     */
180 26
    private static function mapAnnotated(\stdClass $from, string $toClass)
181
    {
182
        try {
183 26
            $resultClass = new \ReflectionClass($toClass);
184
            
185 26
            $rf = new \ReflectionObject($from);
186
            
187 26
            $result = $resultClass->newInstanceWithoutConstructor();
188
            
189 26
            $properties = $rf->getProperties();
190 26
            foreach ($properties as $property) {
191
                // attached property by annotation mapping => map later
192 26
                if (strpos($property->getName(), '.')) {
193 3
                    continue;
194
                }
195
                
196 26
                list ($type, $value) = self::getAnnotatedPropertyValue($from, $toClass, $property, $rf->getNamespaceName());
197
                
198 26
                $result = self::assignPropertyValue($result, $resultClass, $property->getName(), $type, $value);
199
            }
200
            
201 26
            return $result;
202
        } catch (\ReflectionException $exception) {
203
            throw OrmException::fromPrevious($exception);
204
        }
205
    }
206
207
    /**
208
     * Assign the property value to result object via annotation
209
     *
210
     * @param object $result            
211
     * @param \ReflectionProperty $resultClassProperty            
212
     * @param \ReflectionClass $resultClass            
213
     * @param string $propertyName            
214
     * @param mixed $value            
215
     *
216
     * @return bool Whether to continue assigning
217
     */
218 21
    private static function assignAnnotatedPropertyValue($result, \ReflectionProperty $resultClassProperty, \ReflectionClass $resultClass, string $propertyName, $value): bool
219
    {
220 21
        $docComments = $resultClassProperty->getDocComment();
221
        
222 21
        $type = self::getAnnotatedType($docComments, $resultClass->getNamespaceName());
223
        
224 21
        if ("" === ($destinationProperty = self::getAnnotatedColumn($docComments)) || $destinationProperty !== $propertyName || null === $type) {
225 3
            return true;
226
        }
227
        
228 19
        if (! self::isPrimitive($type) && class_exists($type) && ! $value instanceof $type) {
229 1
            return false;
230
        }
231
        
232 18
        $method = sprintf("set%s", ucfirst($resultClassProperty->getName()));
233 18
        if ($resultClass->hasMethod($method)) {
234 18
            $rfMethod = new \ReflectionMethod($resultClass->name, $method);
235 18
            $rfMethod->invoke($result, $value);
236 18
            return false;
237
        }
238
        
239
        return true;
240
    }
241
242
    /**
243
     * Assign the property value to result object
244
     *
245
     * @param object $result            
246
     * @param \ReflectionClass $resultClass            
247
     * @param string $propertyName            
248
     * @param string $type            
249
     * @param mixed $value            
250
     *
251
     * @return object The assigned result object
252
     */
253 26
    private static function assignPropertyValue($result, \ReflectionClass $resultClass, string $propertyName, string $type, $value): AbstractModel
254
    {
255 26
        $method = sprintf("set%s", ucfirst($propertyName));
256
        
257 26
        if ($resultClass->hasMethod($method)) {
258 26
            $rfMethod = new \ReflectionMethod($resultClass->name, $method);
259 26
            $rfMethod->invoke($result, self::convertType($type, $value));
260
        } else {
261 21
            foreach ($resultClass->getProperties() as $resultClassProperty) {
262 21
                if (! self::assignAnnotatedPropertyValue($result, $resultClassProperty, $resultClass, $propertyName, $value)) {
263 21
                    break;
264
                }
265
            }
266
        }
267
        
268 26
        return $result;
269
    }
270
271
    /**
272
     * Parse the @mappedBy annotation
273
     *
274
     * @param string $mappedBy
275
     *            The mappedBy annotation string
276
     *            
277
     * @return array All parsed property attributes of the mappedBy string
278
     */
279 5
    private static function parseMappedBy(string $mappedBy): array
280
    {
281 5
        $mappingOptions = array();
282 5
        foreach (explode(',', $mappedBy) as $mappingOption) {
283 5
        	if(empty($mappingOption)) continue;
284
        	
285 5
            list ($option, $value) = preg_split('/=/', $mappingOption);
286 5
            $mappingOptions[$option] = $value;
287
        }
288
        
289 5
        return $mappingOptions;
290
    }
291
}
292