ClassGenerator::serialize()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * This file is part of the Drest package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @author Lee Davis
9
 * @copyright Copyright (c) Lee Davis <@leedavis81>
10
 * @link https://github.com/leedavis81/drest/blob/master/LICENSE
11
 * @license http://opensource.org/licenses/MIT The MIT X License (MIT)
12
 */
13
namespace Drest;
14
15
use Doctrine\Common\Inflector\Inflector;
16
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ORMClassMetadata;
17
use Zend\Code\Generator\ParameterGenerator;
18
use Zend\Code\Generator;
19
20
/**
21
 * Class generator used to create client classes
22
 * @author Lee
23
 *
24
 */
25
class ClassGenerator
26
{
27
28
    /**
29
     * Header parameter to look for if a request for class info has be done
30
     * @var string HEADER_PARAM
31
     */
32
    const HEADER_PARAM = 'X-DrestCG';
33
34
    /**
35
     * Param types - used in setter method generators
36
     * @var integer
37
     */
38
    const PARAM_TYPE_ITEM = 1;
39
    const PARAM_TYPE_RELATION_SINGLE = 2;
40
    const PARAM_TYPE_RELATION_COLLECTION = 3;
41
42
    /**
43
     * CG classes generated from routeMetaData
44
     * @var array $classes - uses className as the key
45
     */
46
    protected $classes = [];
47
48
    /**
49
     * Entity manager - required to detect relation types and classNames on expose data
50
     * @param EntityManagerRegistry $emr
51
     */
52
    protected $emr;
53
54
    /**
55
     * Create an class generator instance
56
     * @param EntityManagerRegistry $emr
57
     */
58 1
    public function __construct(EntityManagerRegistry $emr)
59
    {
60 1
        $this->emr = $emr;
61 1
    }
62
63
    /**
64
     * Create a class generator instance from provided route metadata.
65
     * Each route will generate it's own unique version of the class (as it will have its own exposure definitions)
66
     * @param  array $classMetadatas
67
     * @return array $object - an array of ClassGenerator objects
68
     */
69 1
    public function create(array $classMetadatas)
70
    {
71 1
        foreach ($classMetadatas as $classMetaData) {
72
            /* @var \Drest\Mapping\ClassMetaData $classMetaData */
73 1
            $expose = [];
74 1
            foreach ($classMetaData->getRoutesMetaData() as $routeMetaData) {
75
                /* @var \Drest\Mapping\RouteMetaData $routeMetaData */
76 1
                $expose = array_merge_recursive($expose, $routeMetaData->getExpose());
77 1
            }
78 1
            $this->recurseParams($expose, $classMetaData->getClassName());
79 1
        }
80
81 1
        serialize($this->classes);
82 1
    }
83
84
    /**
85
     * Return the generated classes in serialized form
86
     * @return string $serialized
87
     */
88 1
    public function serialize()
89
    {
90 1
        return serialize($this->classes);
91
    }
92
93
94
    /**
95
     * Recurse the expose parameters - pass the entities full class name (including namespace)
96
     * @param array  $expose
97
     * @param string $fullClassName
98
     */
99 1
    protected function recurseParams(array $expose, $fullClassName)
100
    {
101
        // get ORM metadata for the current class
102
        /** @var \Doctrine\ORM\Mapping\ClassMetadata $ormClassMetaData */
103 1
        $ormClassMetaData = $this->emr->getManagerForClass($fullClassName)->getClassMetadata($fullClassName);
104
105 1
        if (isset($this->classes[$fullClassName])) {
106 1
            $cg = $this->classes[$fullClassName];
107 1
        } else {
108 1
            $cg = new Generator\ClassGenerator();
109 1
            $cg->setName($fullClassName);
110
111 1
            $short = 'This class was generated by the drest-client tool, and should be treated as a plain data object';
112
            $long = <<<EOT
113
ANY ALTERATIONS WILL BE OVERWRITTEN if the classes are regenerated
114
The variables declared are exposed by the rest endpoint provided when generating these classes.
115
However depending on the operation (GET/POST/PUT etc) used some of these may not be populated / operational.
116 1
EOT;
117 1
            $docBlock = new Generator\DocBlockGenerator($short, $long);
118 1
            $cg->setDocBlock($docBlock);
119
120 1
            $cg->addMethods([$this->getStaticCreateMethod($this->getTargetType($fullClassName))]);
121
        }
122
123 1
        foreach ($expose as $key => $value) {
124 1
            if (is_array($value)) {
125 1
                if ($ormClassMetaData->hasAssociation($key)) {
126 1
                    $this->handleAssocProperty($key, $cg, $ormClassMetaData);
127
128 1
                    $assocMapping = $ormClassMetaData->getAssociationMapping($key);
129 1
                    $this->recurseParams($value, $assocMapping['targetEntity']);
130 1
                }
131 1
            } else {
132 1
                if ($ormClassMetaData->hasAssociation($value)) {
133
                    // This is an association field with no explicit include fields,
134
                    // include add data field (no relations)
135 1
                    $this->handleAssocProperty($value, $cg, $ormClassMetaData);
136
137 1
                    $assocMapping = $ormClassMetaData->getAssociationMapping($value);
138
                    /** @var \Doctrine\ORM\Mapping\ClassMetadata $teCmd */
139 1
                    $teCmd = $this->emr->getManagerForClass($assocMapping['targetEntity'])->getClassMetadata($assocMapping['targetEntity']);
140 1
                    $this->recurseParams($teCmd->getColumnNames(), $assocMapping['targetEntity']);
141 1
                } else {
142 1
                    $this->handleNonAssocProperty($value, $cg);
143
                }
144
            }
145 1
        }
146
147
        // If the class is already set, overwrite it with it additional expose fields
148 1
        $this->classes[$fullClassName] = $cg;
149 1
    }
150
151
    /**
152
     * Build a ::create() method for each data class
153
     * @param  string                               $class - The name of the class returned (self)
154
     * @return \Zend\Code\Generator\MethodGenerator $method
155
     */
156 1
    private function getStaticCreateMethod($class)
157
    {
158 1
        $method = new Generator\MethodGenerator();
159 1
        $method->setDocBlock('@return ' . $class . ' $instance');
160 1
        $method->setBody('return new self();');
161 1
        $method->setName('create');
162 1
        $method->setStatic(true);
163
164 1
        return $method;
165
    }
166
167
    /**
168
     * Create a property instance
169
     * @param  string                      $name - property name
170
     * @return Generator\PropertyGenerator $property
171
     */
172 1
    private function createProperty($name)
173
    {
174 1
        $property = new Generator\PropertyGenerator();
175 1
        $property->setName($name);
176 1
        $property->setVisibility(Generator\AbstractMemberGenerator::FLAG_PUBLIC);
177
178 1
        return $property;
179
    }
180
181
    /**
182
     * get setter methods for a parameter based on type
183
     * @param  Generator\ClassGenerator $cg
184
     * @param  string                   $name        - the parameter name
185
     * @param  int                      $type        - The type of parameter to be handled
186
     * @param  string                   $targetClass - the target class name to be set (only used in relational setters)
187
     * @return array                    $methods
188
     */
189 1
    private function getSetterMethods(&$cg, $name, $type, $targetClass = null)
190
    {
191
        /** @var Generator\MethodGenerator[] $methods **/
192 1
        $methods = [];
193
        switch ($type) {
194 1
            case self::PARAM_TYPE_ITEM:
195 1
                $method = new Generator\MethodGenerator();
196 1
                $method->setDocBlock('@param string $' . $name);
197
198 1
                $method->setParameter(new ParameterGenerator($name));
199 1
                $method->setBody('$this->' . $name . ' = $' . $name . ';');
200
201 1
                $method->setName('set' . $this->camelCaseMethodName($name));
202 1
                $methods[] = $method;
203 1
                break;
204 1
            case self::PARAM_TYPE_RELATION_SINGLE:
205 1
                $method = new Generator\MethodGenerator();
206 1
                $method->setDocBlock('@param ' . $targetClass . ' $' . $name);
207
208 1
                $method->setParameter(new ParameterGenerator($name, $this->getTargetType($targetClass)));
209 1
                $method->setBody('$this->' . $name . ' = $' . $name . ';');
210 1
                $method->setName('set' . $this->camelCaseMethodName($name));
211 1
                $methods[] = $method;
212 1
                break;
213 1
            case self::PARAM_TYPE_RELATION_COLLECTION:
214 1
                $singledName = Inflector::singularize($name);
215 1
                $method = new Generator\MethodGenerator();
216 1
                $method->setDocBlock('@param ' . $targetClass . ' $' . $singledName);
217
218
219 1
                $method->setParameter(new ParameterGenerator($singledName, $this->getTargetType($targetClass)));
220
221 1
                $method->setBody('$this->' . $name . '[] = $' . $singledName . ';');
222 1
                $singleMethodName = 'add' . $this->camelCaseMethodName($singledName);
223 1
                $method->setName($singleMethodName);
224 1
                $methods[] = $method;
225
226 1
                $pluralName = Inflector::pluralize($name);
227 1
                if ($singledName === $pluralName) {
228
                    // Unable to generate a pluralized collection method
229
                    break;
230
                }
231
232 1
                $pluralMethod = new Generator\MethodGenerator();
233 1
                $pluralMethod->setDocBlock('@param array $' . $name);
234
235 1
                $pluralMethod->setName('add' . $this->camelCaseMethodName($pluralName));
236 1
                $pluralMethod->setParameter(new ParameterGenerator($pluralName, 'array'));
237 1
                $body = "foreach (\$$pluralName as \$$singledName) \n{\n";
238 1
                $body .= "    \$this->$singleMethodName(\$$singledName);\n}";
239 1
                $pluralMethod->setBody($body);
240
241 1
                $methods[] = $pluralMethod;
242 1
                break;
243
        }
244
245
        // All setter methods will return $this
246 1
        $methodsSize = sizeof($methods);
247 1
        for ($x = 0; $x < $methodsSize; $x++) {
248 1
            $docBlock = $methods[$x]->getDocBlock();
249 1
            $docBlock->setShortDescription($docBlock->getShortDescription() . "\n@return " . $cg->getName() . ' $this');
250 1
            $methods[$x]->setDocBlock($docBlock);
251 1
            $methods[$x]->setBody($methods[$x]->getBody() . "\nreturn \$this;");
252 1
        }
253
254 1
        return $methods;
255
    }
256
257
    /**
258
     * Handle a non associative property
259
     * @param string                              $name - name of the field
260
     * @param \Zend\Code\Generator\ClassGenerator $cg   - The class generator object to attach to
261
     */
262 1
    private function handleNonAssocProperty($name, Generator\ClassGenerator &$cg)
263
    {
264 1
        $property = $this->createProperty($name);
265 1
        if (!$cg->hasProperty($name)) {
266 1
            $cg->addProperties([$property]);
267 1
            $cg->addMethods($this->getSetterMethods($cg, $name, self::PARAM_TYPE_ITEM));
268 1
        }
269 1
    }
270
271
    /**
272
     * Handle an associative property field
273
     * @param string                              $name             - name of the field
274
     * @param \Zend\Code\Generator\ClassGenerator $cg               - The class generator object to attach to
275
     * @param ORMClassMetadata $ormClassMetaData - The ORM class meta data
276
     */
277 1
    private function handleAssocProperty($name, Generator\ClassGenerator &$cg, ORMClassMetadata $ormClassMetaData)
278
    {
279
        /** @var \Doctrine\ORM\Mapping\ClassMetaData $ormClassMetaData */
280 1
        $assocMapping = $ormClassMetaData->getAssociationMapping($name);
281 1
        $property = $this->createProperty($name);
282
283 1
        if ($assocMapping['type'] & $ormClassMetaData::TO_MANY) {
284
            // This is a collection (should be an Array)
285 1
            $property->setDocBlock('@var array $' . $name);
286 1
            $property->setDefaultValue([]);
287 1
            $paramType = self::PARAM_TYPE_RELATION_COLLECTION;
288 1
        } else {
289
            // This is a single relation
290 1
            $property->setDocBlock('@var ' . $assocMapping['targetEntity'] . ' $' . $name);
291 1
            $paramType = self::PARAM_TYPE_RELATION_SINGLE;
292
        }
293
294 1
        if (!$cg->hasProperty($name)) {
295 1
            $cg->addProperties([$property]);
296 1
            $cg->addMethods($this->getSetterMethods($cg, $name, $paramType, $assocMapping['targetEntity']));
297 1
        }
298 1
    }
299
300
301
    /**
302
     * camel case a parameter into a suitable method name
303
     * @param  string $name
304
     * @return string $name
305
     */
306 1
    private function camelCaseMethodName($name)
307
    {
308 1
        return implode(
309 1
            '',
310 1
            array_map(
311 1
                function ($item) {
312 1
                    return ucfirst($item);
313 1
                },
314 1
                explode('_', $name)
315 1
            )
316 1
        );
317
    }
318
319
    /**
320
     * Get the target type class (excludes any namespace)
321
     * @param  string $targetClass
322
     * @return string
323
     */
324 1
    private function getTargetType($targetClass)
325
    {
326 1
        $parts = explode('\\', $targetClass);
327
328 1
        return (sizeof($parts) > 1) ? implode('\\', array_slice($parts, 1)) : $targetClass;
329
    }
330
}
331