Completed
Push — master ( eb83f4...bb238e )
by Maximilian
14s
created

src/Model/ModelManager.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
     * @return FieldDescription
133
     *
134
     * @throws \RunTimeException if $name is not a string
135
     */
136
    public function getNewFieldDescriptionInstance($class, $name, array $options = array())
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])) {
149
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$name]);
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 = array())
163
    {
164
        return $this->dm->getRepository($class)->findBy($criteria);
165
    }
166
167
    /**
168
     * {@inheritdoc}
169
     */
170
    public function findOneBy($class, array $criteria = array())
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
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
$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
     * @return ProxyQueryInterface
210
     *
211
     * @throws \InvalidArgumentException if alias is not a string or an empty string
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 array($path);
255
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    public function getIdentifierFieldNames($class)
261
    {
262
        return array($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
        return;
303
    }
304
305
    /**
306
     * {@inheritdoc}
307
     */
308
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx)
309
    {
310
        /* @var $queryProxy ProxyQuery */
311
        $qb = $queryProxy->getQueryBuilder();
312
313
        $orX = $qb->andWhere()->orX();
314
315
        foreach ($idx as $id) {
316
            $path = $this->getBackendId($id);
317
            $orX->same($path, $queryProxy->getAlias());
318
        }
319
    }
320
321
    /**
322
     * Add leading slash to construct valid phpcr document id.
323
     *
324
     * The phpcr-odm QueryBuilder uses absolute paths and expects id´s to start with a forward slash
325
     * because SonataAdmin uses object id´s for constructing URL´s it has to use id´s without the
326
     * leading slash.
327
     *
328
     * @param string $id
329
     *
330
     * @return string
331
     */
332
    public function getBackendId($id)
333
    {
334
        return substr($id, 0, 1) === '/' ? $id : '/'.$id;
335
    }
336
337
    /**
338
     * {@inheritdoc}
339
     *
340
     * @throws ModelManagerException if anything goes wrong during query execution
341
     */
342
    public function batchDelete($class, ProxyQueryInterface $queryProxy)
343
    {
344
        try {
345
            $i = 0;
346
            $res = $queryProxy->execute();
347
            foreach ($res as $object) {
348
                $this->dm->remove($object);
349
350
                if ((++$i % 20) == 0) {
351
                    $this->dm->flush();
352
                    $this->dm->clear();
353
                }
354
            }
355
356
            $this->dm->flush();
357
            $this->dm->clear();
358
        } catch (\Exception $e) {
359
            throw new ModelManagerException('', 0, $e);
360
        }
361
    }
362
363
    /**
364
     * {@inheritdoc}
365
     *
366
     * @return object
367
     */
368
    public function getModelInstance($class)
369
    {
370
        return new $class();
371
    }
372
373
    /**
374
     * {@inheritdoc}
375
     */
376
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
377
    {
378
        $values = $datagrid->getValues();
379
380
        if ($fieldDescription->getName() == $values['_sort_by']->getName()) {
381
            if ($values['_sort_order'] == 'ASC') {
382
                $values['_sort_order'] = 'DESC';
383
            } else {
384
                $values['_sort_order'] = 'ASC';
385
            }
386
387
            $values['_sort_by'] = $fieldDescription->getName();
388
        } else {
389
            $values['_sort_order'] = 'ASC';
390
            $values['_sort_by'] = $fieldDescription->getName();
391
        }
392
393
        return array('filter' => $values);
394
    }
395
396
    /**
397
     * {@inheritdoc}
398
     */
399
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
400
    {
401
        $values = $datagrid->getValues();
402
403
        $values['_sort_by'] = $values['_sort_by']->getName();
404
        $values['_page'] = $page;
405
406
        return array('filter' => $values);
407
    }
408
409
    /**
410
     * {@inheritdoc}
411
     */
412
    public function getDefaultSortValues($class)
413
    {
414
        return array(
415
            '_sort_order' => 'ASC',
416
            '_sort_by' => $this->getModelIdentifier($class),
417
            '_page' => 1,
418
        );
419
    }
420
421
    /**
422
     * {@inheritdoc}
423
     *
424
     * @return object
425
     */
426
    public function modelTransform($class, $instance)
427
    {
428
        return $instance;
429
    }
430
431
    /**
432
     * {@inheritdoc}
433
     *
434
     * @return object
435
     *
436
     * @throws NoSuchPropertyException if the class has no magic setter and
437
     *                                 public property for a field in array
438
     */
439
    public function modelReverseTransform($class, array $array = array())
440
    {
441
        $instance = $this->getModelInstance($class);
442
        $metadata = $this->getMetadata($class);
443
444
        $reflClass = $metadata->reflClass;
445
        foreach ($array as $name => $value) {
446
            $reflection_property = false;
447
            // property or association ?
448
            if (array_key_exists($name, $metadata->fieldMappings)) {
449
                $property = $metadata->fieldMappings[$name]['fieldName'];
450
                $reflection_property = $metadata->reflFields[$name];
451
            } elseif (array_key_exists($name, $metadata->associationMappings)) {
452
                $property = $metadata->associationMappings[$name]['fieldName'];
0 ignored issues
show
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...
453
            } else {
454
                $property = $name;
455
            }
456
457
            // TODO: use PropertyAccess https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/issues/187
458
            $setter = 'set'.$this->camelize($name);
459
460
            if ($reflClass->hasMethod($setter)) {
461
                if (!$reflClass->getMethod($setter)->isPublic()) {
462
                    throw new NoSuchPropertyException(sprintf('Method "%s()" is not public in class "%s"', $setter, $reflClass->getName()));
463
                }
464
465
                $instance->$setter($value);
466
            } elseif ($reflClass->hasMethod('__set')) {
467
                // needed to support magic method __set
468
                $instance->$property = $value;
469
            } elseif ($reflClass->hasProperty($property)) {
470
                if (!$reflClass->getProperty($property)->isPublic()) {
471
                    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)));
472
                }
473
474
                $instance->$property = $value;
475
            } elseif ($reflection_property) {
476
                $reflection_property->setValue($instance, $value);
477
            }
478
        }
479
480
        return $instance;
481
    }
482
483
    /**
484
     * {@inheritdoc}
485
     */
486
    public function getModelCollectionInstance($class)
487
    {
488
        return new ArrayCollection();
489
    }
490
491
    /**
492
     * {@inheritdoc}
493
     */
494
    public function collectionClear(&$collection)
495
    {
496
        return $collection->clear();
497
    }
498
499
    /**
500
     * {@inheritdoc}
501
     */
502
    public function collectionHasElement(&$collection, &$element)
503
    {
504
        return $collection->contains($element);
505
    }
506
507
    /**
508
     * {@inheritdoc}
509
     */
510
    public function collectionAddElement(&$collection, &$element)
511
    {
512
        return $collection->add($element);
513
    }
514
515
    /**
516
     * {@inheritdoc}
517
     */
518
    public function collectionRemoveElement(&$collection, &$element)
519
    {
520
        return $collection->removeElement($element);
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
527
    {
528
        throw new \RuntimeException('Datasourceiterator not implemented.');
529
    }
530
531
    /**
532
     * {@inheritdoc}
533
     *
534
     * Not really implemented.
535
     */
536
    public function getExportFields($class)
537
    {
538
        return array();
539
    }
540
541
    /**
542
     * Method taken from PropertyPath.
543
     *
544
     * NEXT_MAJOR: remove when doing https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/issues/187
545
     *
546
     * @param string $property
547
     *
548
     * @return string
549
     *
550
     * @deprecated
551
     */
552
    protected function camelize($property)
553
    {
554
        return preg_replace(array('/(^|_)+(.)/e', '/\.(.)/e'), array("strtoupper('\\2')", "'_'.strtoupper('\\1')"), $property);
555
    }
556
}
557