Completed
Push — master ( 126036...3fa63d )
by Maik
07:56
created

OrmMapping::parseMappedBy()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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