Completed
Pull Request — 2.x (#521)
by Maximilian
02:22
created

ModelManager::modelReverseTransform()   B

Complexity

Conditions 10
Paths 22

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 43
rs 7.6666
c 0
b 0
f 0
cc 10
nc 22
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\DoctrinePHPCRAdminBundle\Model;
13
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\Common\Util\ClassUtils;
16
use Doctrine\ODM\PHPCR\DocumentManager;
17
use Doctrine\ODM\PHPCR\Mapping\ClassMetadata;
18
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
19
use Sonata\AdminBundle\Datagrid\DatagridInterface;
20
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
21
use Sonata\AdminBundle\Exception\ModelManagerException;
22
use Sonata\AdminBundle\Model\ModelManagerInterface;
23
use Sonata\DoctrinePHPCRAdminBundle\Admin\FieldDescription;
24
use Sonata\DoctrinePHPCRAdminBundle\Datagrid\ProxyQuery;
25
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
26
27
class ModelManager implements ModelManagerInterface
28
{
29
    /**
30
     * @var DocumentManager
31
     */
32
    protected $dm;
33
34
    /**
35
     * @param DocumentManager $dm
36
     */
37
    public function __construct(DocumentManager $dm)
38
    {
39
        $this->dm = $dm;
40
    }
41
42
    /**
43
     * Returns the related model's metadata.
44
     *
45
     * @param string $class
46
     *
47
     * @return ClassMetadata
48
     */
49
    public function getMetadata($class)
50
    {
51
        return $this->dm->getMetadataFactory()->getMetadataFor($class);
52
    }
53
54
    /**
55
     * Returns true is the model has some metadata.
56
     *
57
     * @param string $class
58
     *
59
     * @return bool
60
     */
61
    public function hasMetadata($class)
62
    {
63
        return $this->dm->getMetadataFactory()->hasMetadataFor($class);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     *
69
     * @throws ModelManagerException if the document manager throws any exception
70
     */
71
    public function create($object)
72
    {
73
        try {
74
            $this->dm->persist($object);
75
            $this->dm->flush();
76
        } catch (\Exception $e) {
77
            throw new ModelManagerException('', 0, $e);
78
        }
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     *
84
     * @throws ModelManagerException if the document manager throws any exception
85
     */
86
    public function update($object)
87
    {
88
        try {
89
            $this->dm->persist($object);
90
            $this->dm->flush();
91
        } catch (\Exception $e) {
92
            throw new ModelManagerException('', 0, $e);
93
        }
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * @throws ModelManagerException if the document manager throws any exception
100
     */
101
    public function delete($object)
102
    {
103
        try {
104
            $this->dm->remove($object);
105
            $this->dm->flush();
106
        } catch (\Exception $e) {
107
            throw new ModelManagerException('', 0, $e);
108
        }
109
    }
110
111
    /**
112
     * Find one object from the given class repository.
113
     *
114
     * {@inheritdoc}
115
     */
116
    public function find($class, $id)
117
    {
118
        if (!isset($id)) {
119
            return;
120
        }
121
122
        if (null === $class) {
123
            return $this->dm->find(null, $id);
124
        }
125
126
        return $this->dm->getRepository($class)->find($id);
127
    }
128
129
    /**
130
     * {@inheritdoc}
131
     *
132
     * @throws \RunTimeException if $name is not a string
133
     *
134
     * @return FieldDescription
135
     */
136
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
137
    {
138
        if (!is_string($name)) {
139
            throw new \RunTimeException('The name argument must be a string');
140
        }
141
142
        $metadata = $this->getMetadata($class);
143
144
        $fieldDescription = new FieldDescription();
145
        $fieldDescription->setName($name);
146
        $fieldDescription->setOptions($options);
147
148
        if (isset($metadata->associationMappings[$name])) {
0 ignored issues
show
Bug introduced by
The property associationMappings does not seem to exist. Did you mean mappings?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
149
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$name]);
0 ignored issues
show
Bug introduced by
The property associationMappings does not seem to exist. Did you mean mappings?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
150
        }
151
152
        if (isset($metadata->fieldMappings[$name])) {
153
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$name]);
154
        }
155
156
        return $fieldDescription;
157
    }
158
159
    /**
160
     * {@inheritdoc}
161
     */
162
    public function findBy($class, array $criteria = [])
163
    {
164
        return $this->dm->getRepository($class)->findBy($criteria);
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function findOneBy($class, array $criteria = [])
171
    {
172
        return $this->dm->getRepository($class)->findOneBy($criteria);
173
    }
174
175
    /**
176
     * @return DocumentManager The PHPCR-ODM document manager responsible for
177
     *                         this model
178
     */
179
    public function getDocumentManager()
180
    {
181
        return $this->dm;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     *
187
     * @return FieldDescriptionInterface
188
     */
189
    public function getParentFieldDescription($parentAssociationMapping, $class)
190
    {
191
        $fieldName = $parentAssociationMapping['fieldName'];
192
193
        $metadata = $this->getMetadata($class);
194
195
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
0 ignored issues
show
Bug introduced by
The property associationMappings does not seem to exist. Did you mean mappings?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
196
197
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
198
        $fieldDescription->setName($parentAssociationMapping);
0 ignored issues
show
Documentation introduced by
$parentAssociationMapping is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
        $fieldDescription->setAssociationMapping($associatingMapping);
200
201
        return $fieldDescription;
202
    }
203
204
    /**
205
     * @param string $class the fully qualified class name to search for
206
     * @param string $alias alias to use for this class when accessing fields,
207
     *                      defaults to 'a'
208
     *
209
     * @throws \InvalidArgumentException if alias is not a string or an empty string
210
     *
211
     * @return ProxyQueryInterface
212
     */
213
    public function createQuery($class, $alias = 'a')
214
    {
215
        $qb = $this->getDocumentManager()->createQueryBuilder();
216
        $qb->from()->document($class, $alias);
217
218
        return new ProxyQuery($qb, $alias);
219
    }
220
221
    /**
222
     * @param ProxyQuery $query
223
     *
224
     * @return mixed
225
     */
226
    public function executeQuery($query)
227
    {
228
        return $query->execute();
229
    }
230
231
    /**
232
     * {@inheritdoc}
233
     */
234
    public function getModelIdentifier($classname)
235
    {
236
        return $this->getMetadata($classname)->identifier;
237
    }
238
239
    /**
240
     * Transforms the document into the PHPCR path.
241
     *
242
     * Note: This is returning an array because Doctrine ORM for example can
243
     * have multiple identifiers, e.g. if the primary key is composed of
244
     * several columns. We only ever have one, but return that wrapped into an
245
     * array to adhere to the interface.
246
     *
247
     * {@inheritdoc}
248
     */
249
    public function getIdentifierValues($document)
250
    {
251
        $class = $this->getMetadata(ClassUtils::getClass($document));
252
        $path = $class->reflFields[$class->identifier]->getValue($document);
253
254
        return [$path];
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function getIdentifierFieldNames($class)
261
    {
262
        return [$this->getModelIdentifier($class)];
263
    }
264
265
    /**
266
     * This is just taking the id out of the array again.
267
     *
268
     * {@inheritdoc}
269
     *
270
     * @throws \InvalidArgumentException if $document is not an object or null
271
     */
272
    public function getNormalizedIdentifier($document)
273
    {
274
        if (is_scalar($document)) {
275
            throw new \InvalidArgumentException('Invalid argument, object or null required');
276
        }
277
278
        // the document is not managed
279
        if (!$document || !$this->getDocumentManager()->contains($document)) {
280
            return;
281
        }
282
283
        $values = $this->getIdentifierValues($document);
284
285
        return $values[0];
286
    }
287
288
    /**
289
     * Currently only the leading slash is removed.
290
     *
291
     * @param object $document
292
     *
293
     * @return null|string
294
     */
295
    public function getUrlsafeIdentifier($document)
296
    {
297
        $id = $this->getNormalizedIdentifier($document);
298
        if (null !== $id) {
299
            return substr($id, 1);
300
        }
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
307
    {
308
        /* @var $queryProxy ProxyQuery */
309
        $qb = $queryProxy->getQueryBuilder();
310
311
        $orX = $qb->andWhere()->orX();
312
313
        foreach ($idx as $id) {
314
            $path = $this->getBackendId($id);
315
            $orX->same($path, $queryProxy->getAlias());
316
        }
317
    }
318
319
    /**
320
     * Add leading slash to construct valid phpcr document id.
321
     *
322
     * The phpcr-odm QueryBuilder uses absolute paths and expects id´s to start with a forward slash
323
     * because SonataAdmin uses object id´s for constructing URL´s it has to use id´s without the
324
     * leading slash.
325
     *
326
     * @param string $id
327
     *
328
     * @return string
329
     */
330
    public function getBackendId($id)
331
    {
332
        return '/' === substr($id, 0, 1) ? $id : '/'.$id;
333
    }
334
335
    /**
336
     * {@inheritdoc}
337
     *
338
     * @throws ModelManagerException if anything goes wrong during query execution
339
     */
340
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
341
    {
342
        try {
343
            $i = 0;
344
            $res = $queryProxy->execute();
345
            foreach ($res as $object) {
346
                $this->dm->remove($object);
347
348
                if (0 == (++$i % 20)) {
349
                    $this->dm->flush();
350
                    $this->dm->clear();
351
                }
352
            }
353
354
            $this->dm->flush();
355
            $this->dm->clear();
356
        } catch (\Exception $e) {
357
            throw new ModelManagerException('', 0, $e);
358
        }
359
    }
360
361
    /**
362
     * {@inheritdoc}
363
     *
364
     * @return object
365
     */
366
    public function getModelInstance($class)
367
    {
368
        return new $class();
369
    }
370
371
    /**
372
     * {@inheritdoc}
373
     */
374
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
375
    {
376
        $values = $datagrid->getValues();
377
378
        if ($fieldDescription->getName() == $values['_sort_by']->getName()) {
379
            if ('ASC' == $values['_sort_order']) {
380
                $values['_sort_order'] = 'DESC';
381
            } else {
382
                $values['_sort_order'] = 'ASC';
383
            }
384
385
            $values['_sort_by'] = $fieldDescription->getName();
386
        } else {
387
            $values['_sort_order'] = 'ASC';
388
            $values['_sort_by'] = $fieldDescription->getName();
389
        }
390
391
        return ['filter' => $values];
392
    }
393
394
    /**
395
     * {@inheritdoc}
396
     */
397
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
398
    {
399
        $values = $datagrid->getValues();
400
401
        $values['_sort_by'] = $values['_sort_by']->getName();
402
        $values['_page'] = $page;
403
404
        return ['filter' => $values];
405
    }
406
407
    /**
408
     * {@inheritdoc}
409
     */
410
    public function getDefaultSortValues($class)
411
    {
412
        return [
413
            '_sort_order' => 'ASC',
414
            '_sort_by' => $this->getModelIdentifier($class),
415
            '_page' => 1,
416
        ];
417
    }
418
419
    /**
420
     * {@inheritdoc}
421
     *
422
     * @return object
423
     */
424
    public function modelTransform($class, $instance)
425
    {
426
        return $instance;
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     *
432
     * @throws NoSuchPropertyException if the class has no magic setter and
433
     *                                 public property for a field in array
434
     *
435
     * @return object
436
     */
437
    public function modelReverseTransform($class, array $array = [])
438
    {
439
        $instance = $this->getModelInstance($class);
440
        $metadata = $this->getMetadata($class);
441
442
        $reflClass = $metadata->reflClass;
443
        foreach ($array as $name => $value) {
444
            $reflection_property = false;
445
            // property or association ?
446
            if (array_key_exists($name, $metadata->fieldMappings)) {
447
                $property = $metadata->fieldMappings[$name]['fieldName'];
448
                $reflection_property = $metadata->reflFields[$name];
449
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
0 ignored issues
show
Bug introduced by
The property associationMappings does not seem to exist. Did you mean mappings?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
450
                $property = $metadata->associationMappings[$name]['fieldName'];
0 ignored issues
show
Bug introduced by
The property associationMappings does not seem to exist. Did you mean mappings?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
451
            } else {
452
                $property = $name;
453
            }
454
455
            // TODO: use PropertyAccess https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/issues/187
456
            $setter = 'set'.$this->camelize($name);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\DoctrinePHPCRAdmi...odelManager::camelize() has been deprecated.

This method has been deprecated.

Loading history...
457
458
            if ($reflClass->hasMethod($setter)) {
459
                if (!$reflClass->getMethod($setter)->isPublic()) {
460
                    throw new NoSuchPropertyException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
0 ignored issues
show
Bug introduced by
Consider using $reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
461
                }
462
463
                $instance->$setter($value);
464
            } elseif ($reflClass->hasMethod('__set')) {
465
                // needed to support magic method __set
466
                $instance->$property = $value;
467
            } elseif ($reflClass->hasProperty($property)) {
468
                if (!$reflClass->getProperty($property)->isPublic()) {
469
                    throw new NoSuchPropertyException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?', $property, $reflClass->getName(), ucfirst($property)));
0 ignored issues
show
Bug introduced by
Consider using $reflClass->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
470
                }
471
472
                $instance->$property = $value;
473
            } elseif ($reflection_property) {
474
                $reflection_property->setValue($instance, $value);
475
            }
476
        }
477
478
        return $instance;
479
    }
480
481
    /**
482
     * {@inheritdoc}
483
     */
484
    public function getModelCollectionInstance($class)
485
    {
486
        return new ArrayCollection();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Doctrine\Com...ions\ArrayCollection(); (Doctrine\Common\Collections\ArrayCollection) is incompatible with the return type declared by the interface Sonata\AdminBundle\Model...ModelCollectionInstance of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
487
    }
488
489
    /**
490
     * {@inheritdoc}
491
     */
492
    public function collectionClear(&$collection)
493
    {
494
        return $collection->clear();
495
    }
496
497
    /**
498
     * {@inheritdoc}
499
     */
500
    public function collectionHasElement(&$collection, &$element)
501
    {
502
        return $collection->contains($element);
503
    }
504
505
    /**
506
     * {@inheritdoc}
507
     */
508
    public function collectionAddElement(&$collection, &$element)
509
    {
510
        return $collection->add($element);
511
    }
512
513
    /**
514
     * {@inheritdoc}
515
     */
516
    public function collectionRemoveElement(&$collection, &$element)
517
    {
518
        return $collection->removeElement($element);
519
    }
520
521
    /**
522
     * {@inheritdoc}
523
     */
524
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
525
    {
526
        throw new \RuntimeException('Datasourceiterator not implemented.');
527
    }
528
529
    /**
530
     * {@inheritdoc}
531
     *
532
     * Not really implemented.
533
     */
534
    public function getExportFields($class)
535
    {
536
        return [];
537
    }
538
539
    /**
540
     * Method taken from PropertyPath.
541
     *
542
     * NEXT_MAJOR: remove when doing https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/issues/187
543
     *
544
     * @param string $property
545
     *
546
     * @return string
547
     *
548
     * @deprecated
549
     */
550
    protected function camelize($property)
551
    {
552
        return preg_replace(['/(^|_)+(.)/e', '/\.(.)/e'], ["strtoupper('\\2')", "'_'.strtoupper('\\1')"], $property);
553
    }
554
}
555