Completed
Push — master ( a33308...36ba79 )
by Maximilian
01:58
created

src/Model/ModelManager.php (1 issue)

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];
196
197
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
198
        $fieldDescription->setName($parentAssociationMapping);
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'];
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);
0 ignored issues
show
Deprecated Code introduced by
The method Sonata\DoctrinePHPCRAdmi...odelManager::camelize() has been deprecated.

This method has been deprecated.

Loading history...
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