Completed
Push — 3.x-dev-kit ( e63a8f )
by
unknown
02:51
created

ModelManager::getIdentifierValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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\DoctrineMongoDBAdminBundle\Model;
13
14
use Doctrine\ODM\MongoDB\Query\Builder;
15
use Exporter\Source\DoctrineODMQuerySourceIterator;
16
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
17
use Sonata\AdminBundle\Datagrid\DatagridInterface;
18
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
19
use Sonata\AdminBundle\Model\ModelManagerInterface;
20
use Sonata\DoctrineMongoDBAdminBundle\Admin\FieldDescription;
21
use Sonata\DoctrineMongoDBAdminBundle\Datagrid\ProxyQuery;
22
use Symfony\Bridge\Doctrine\ManagerRegistry;
23
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
24
25
class ModelManager implements ModelManagerInterface
26
{
27
    const ID_SEPARATOR = '-';
28
    protected $registry;
29
30
    /**
31
     * @param \Symfony\Bridge\Doctrine\ManagerRegistry $registry
32
     */
33
    public function __construct(ManagerRegistry $registry)
34
    {
35
        $this->registry = $registry;
36
    }
37
38
    /**
39
     * {@inheritdoc}
40
     */
41
    public function getMetadata($class)
42
    {
43
        return $this->getDocumentManager($class)->getMetadataFactory()->getMetadataFor($class);
44
    }
45
46
    /**
47
     * Returns the model's metadata holding the fully qualified property, and the last
48
     * property name.
49
     *
50
     * @param string $baseClass        The base class of the model holding the fully qualified property.
51
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
52
     *                                 property string)
53
     *
54
     * @return array(
0 ignored issues
show
Documentation introduced by
The doc-type array( could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
55
     *                \Doctrine\ODM\MongoDB\Mapping\ClassMetadata $parentMetadata,
56
     *                string $lastPropertyName,
57
     *                array $parentAssociationMappings
58
     *                )
59
     */
60
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
61
    {
62
        $nameElements = explode('.', $propertyFullName);
63
        $lastPropertyName = array_pop($nameElements);
64
        $class = $baseClass;
65
        $parentAssociationMappings = array();
66
67
        foreach ($nameElements as $nameElement) {
68
            $metadata = $this->getMetadata($class);
69
            $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
70
            $class = $metadata->getAssociationTargetClass($nameElement);
71
        }
72
73
        return array($this->getMetadata($class), $lastPropertyName, $parentAssociationMappings);
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function hasMetadata($class)
80
    {
81
        return $this->getDocumentManager($class)->getMetadataFactory()->hasMetadataFor($class);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function getNewFieldDescriptionInstance($class, $name, array $options = array())
88
    {
89
        if (!is_string($name)) {
90
            throw new \RunTimeException('The name argument must be a string');
91
        }
92
93
        if (!isset($options['route']['name'])) {
94
            $options['route']['name'] = 'edit';
95
        }
96
97
        if (!isset($options['route']['parameters'])) {
98
            $options['route']['parameters'] = array();
99
        }
100
101
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
102
103
        $fieldDescription = new FieldDescription();
104
        $fieldDescription->setName($name);
105
        $fieldDescription->setOptions($options);
106
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
107
108
        /* @var ClassMetadata */
109
        if (isset($metadata->associationMappings[$propertyName])) {
110
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
111
        }
112
113
        if (isset($metadata->fieldMappings[$propertyName])) {
114
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
115
        }
116
117
        return $fieldDescription;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function create($object)
124
    {
125
        $documentManager = $this->getDocumentManager($object);
126
        $documentManager->persist($object);
127
        $documentManager->flush();
128
    }
129
130
    /**
131
     * {@inheritdoc}
132
     */
133
    public function update($object)
134
    {
135
        $documentManager = $this->getDocumentManager($object);
136
        $documentManager->persist($object);
137
        $documentManager->flush();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function delete($object)
144
    {
145
        $documentManager = $this->getDocumentManager($object);
0 ignored issues
show
Documentation introduced by
$object is of type object, 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...
146
        $documentManager->remove($object);
147
        $documentManager->flush();
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153
    public function find($class, $id)
154
    {
155
        if (!isset($id)) {
156
            return;
157
        }
158
159
        $documentManager = $this->getDocumentManager($class);
160
161
        if (is_numeric($id)) {
162
            $value = $documentManager->getRepository($class)->find(intval($id));
163
164
            if (!empty($value)) {
165
                return $value;
166
            }
167
        }
168
169
        return $documentManager->getRepository($class)->find($id);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function findBy($class, array $criteria = array())
176
    {
177
        return $this->getDocumentManager($class)->getRepository($class)->findBy($criteria);
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function findOneBy($class, array $criteria = array())
184
    {
185
        return $this->getDocumentManager($class)->getRepository($class)->findOneBy($criteria);
186
    }
187
188
    /**
189
     * @param string $class
190
     *
191
     * @throw \RuntimeException
192
     *
193
     * @return \Doctrine\ODM\MongoDB\DocumentManager
194
     */
195
    public function getDocumentManager($class)
196
    {
197
        if (is_object($class)) {
198
            $class = get_class($class);
199
        }
200
201
        $dm = $this->registry->getManagerForClass($class);
202
203
        if (!$dm) {
204
            throw new \RuntimeException(sprintf('No document manager defined for class %s', $class));
205
        }
206
207
        return $dm;
208
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213
    public function getParentFieldDescription($parentAssociationMapping, $class)
214
    {
215
        $fieldName = $parentAssociationMapping['fieldName'];
216
217
        $metadata = $this->getMetadata($class);
218
219
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
220
221
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
222
        $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...
223
        $fieldDescription->setAssociationMapping($associatingMapping);
224
225
        return $fieldDescription;
226
    }
227
228
    /**
229
     * {@inheritdoc}
230
     */
231
    public function createQuery($class, $alias = 'o')
232
    {
233
        $repository = $this->getDocumentManager($class)->getRepository($class);
234
235
        return new ProxyQuery($repository->createQueryBuilder());
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241
    public function executeQuery($query)
242
    {
243
        if ($query instanceof Builder) {
244
            return $query->getQuery()->execute();
245
        }
246
247
        return $query->execute();
248
    }
249
250
    /**
251
     * {@inheritdoc}
252
     */
253
    public function getModelIdentifier($class)
254
    {
255
        return $this->getMetadata($class)->identifier;
256
    }
257
258
    /**
259
     * {@inheritdoc}
260
     */
261
    public function getIdentifierValues($document)
262
    {
263
        return array($this->getDocumentManager($document)->getUnitOfWork()->getDocumentIdentifier($document));
0 ignored issues
show
Documentation introduced by
$document is of type object, 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...
264
    }
265
266
    /**
267
     * {@inheritdoc}
268
     */
269
    public function getIdentifierFieldNames($class)
270
    {
271
        return array($this->getMetadata($class)->getIdentifier());
272
    }
273
274
    /**
275
     * {@inheritdoc}
276
     */
277
    public function getNormalizedIdentifier($document)
278
    {
279
        if (is_scalar($document)) {
280
            throw new \RunTimeException('Invalid argument, object or null required');
281
        }
282
283
        // the entities is not managed
284
        if (!$document || !$this->getDocumentManager($document)->getUnitOfWork()->isInIdentityMap($document)) {
0 ignored issues
show
Documentation introduced by
$document is of type object, 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...
285
            return;
286
        }
287
288
        $values = $this->getIdentifierValues($document);
289
290
        return implode(self::ID_SEPARATOR, $values);
291
    }
292
293
    /**
294
     * {@inheritdoc}
295
     */
296
    public function getUrlsafeIdentifier($entity)
297
    {
298
        return $this->getNormalizedIdentifier($entity);
299
    }
300
301
    /**
302
     * {@inheritdoc}
303
     */
304
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
305
    {
306
        $queryBuilder = $queryProxy->getQueryBuilder();
307
        $queryBuilder->field('_id')->in($idx);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
314
    {
315
        /** @var Query $queryBuilder */
316
        $queryBuilder = $queryProxy->getQuery();
317
318
        $documentManager = $this->getDocumentManager($class);
319
320
        $i = 0;
321
        foreach ($queryBuilder->execute() as $object) {
322
            $documentManager->remove($object);
323
324
            if ((++$i % 20) == 0) {
325
                $documentManager->flush();
326
                $documentManager->clear();
327
            }
328
        }
329
330
        $documentManager->flush();
331
        $documentManager->clear();
332
    }
333
334
    /**
335
     * {@inheritdoc}
336
     */
337
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
338
    {
339
        $datagrid->buildPager();
340
        $query = $datagrid->getQuery();
341
342
        $query->setFirstResult($firstResult);
343
        $query->setMaxResults($maxResult);
344
345
        return new DoctrineODMQuerySourceIterator($query instanceof ProxyQuery ? $query->getQuery() : $query, $fields);
0 ignored issues
show
Bug introduced by
The method getQuery() does not exist on Sonata\DoctrineMongoDBAd...dle\Datagrid\ProxyQuery. Did you maybe mean getQueryBuilder()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
346
    }
347
348
    /**
349
     * {@inheritdoc}
350
     */
351
    public function getExportFields($class)
352
    {
353
        $metadata = $this->getDocumentManager($class)->getClassMetadata($class);
354
355
        return $metadata->getFieldNames();
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     */
361
    public function getModelInstance($class)
362
    {
363
        return new $class();
364
    }
365
366
    /**
367
     * {@inheritdoc}
368
     */
369
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
370
    {
371
        $values = $datagrid->getValues();
372
373
        if ($fieldDescription->getName() == $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
374
            if ($values['_sort_order'] == 'ASC') {
375
                $values['_sort_order'] = 'DESC';
376
            } else {
377
                $values['_sort_order'] = 'ASC';
378
            }
379
        } else {
380
            $values['_sort_order'] = 'ASC';
381
        }
382
383
        $values['_sort_by'] = is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') :  $fieldDescription->getName();
384
385
        return array('filter' => $values);
386
    }
387
388
    /**
389
     * {@inheritdoc}
390
     */
391
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
392
    {
393
        $values = $datagrid->getValues();
394
395
        $values['_sort_by'] = $values['_sort_by']->getName();
396
        $values['_page'] = $page;
397
398
        return array('filter' => $values);
399
    }
400
401
    /**
402
     * {@inheritdoc}
403
     */
404
    public function getDefaultSortValues($class)
405
    {
406
        return array(
407
            '_sort_order' => 'ASC',
408
            '_sort_by' => $this->getModelIdentifier($class),
409
            '_page' => 1,
410
            '_per_page' => 25,
411
        );
412
    }
413
414
    /**
415
     * {@inheritdoc}
416
     */
417
    public function modelTransform($class, $instance)
418
    {
419
        return $instance;
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     */
425
    public function modelReverseTransform($class, array $array = array())
426
    {
427
        $instance = $this->getModelInstance($class);
428
        $metadata = $this->getMetadata($class);
429
430
        $reflClass = $metadata->reflClass;
431
        foreach ($array as $name => $value) {
432
            $reflection_property = false;
433
            // property or association ?
434
            if (array_key_exists($name, $metadata->fieldMappings)) {
435
                $property = $metadata->fieldMappings[$name]['fieldName'];
436
                $reflection_property = $metadata->reflFields[$name];
437
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
438
                $property = $metadata->associationMappings[$name]['fieldName'];
439
            } else {
440
                $property = $name;
441
            }
442
443
            $setter = 'set'.$this->camelize($name);
444
445
            if ($reflClass->hasMethod($setter)) {
446
                if (!$reflClass->getMethod($setter)->isPublic()) {
447
                    throw new PropertyAccessDeniedException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
448
                }
449
450
                $instance->$setter($value);
451
            } elseif ($reflClass->hasMethod('__set')) {
452
                // needed to support magic method __set
453
                $instance->$property = $value;
454
            } elseif ($reflClass->hasProperty($property)) {
455
                if (!$reflClass->getProperty($property)->isPublic()) {
456
                    throw new PropertyAccessDeniedException(sprintf('Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?', $property, $reflClass->getName(), ucfirst($property)));
457
                }
458
459
                $instance->$property = $value;
460
            } elseif ($reflection_property) {
461
                $reflection_property->setValue($instance, $value);
462
            }
463
        }
464
465
        return $instance;
466
    }
467
468
    /**
469
     * {@inheritdoc}
470
     */
471
    public function getModelCollectionInstance($class)
472
    {
473
        return new \Doctrine\Common\Collections\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...
474
    }
475
476
    /**
477
     * {@inheritdoc}
478
     */
479
    public function collectionClear(&$collection)
480
    {
481
        return $collection->clear();
482
    }
483
484
    /**
485
     * {@inheritdoc}
486
     */
487
    public function collectionHasElement(&$collection, &$element)
488
    {
489
        return $collection->contains($element);
490
    }
491
492
    /**
493
     * {@inheritdoc}
494
     */
495
    public function collectionAddElement(&$collection, &$element)
496
    {
497
        return $collection->add($element);
498
    }
499
500
    /**
501
     * {@inheritdoc}
502
     */
503
    public function collectionRemoveElement(&$collection, &$element)
504
    {
505
        return $collection->removeElement($element);
506
    }
507
508
    /**
509
     * method taken from PropertyPath.
510
     *
511
     * @param string $property
512
     *
513
     * @return mixed
514
     */
515
    protected function camelize($property)
516
    {
517
        return preg_replace(array('/(^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\2')", "'_'.strtoupper('\\1')"), $property);
518
    }
519
}
520