Completed
Push — master ( 9ea162...5a7237 )
by Grégoire
01:38
created

ModelManager::getValueFromType()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\DoctrineORMAdminBundle\Model;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Doctrine\DBAL\DBALException;
18
use Doctrine\DBAL\LockMode;
19
use Doctrine\DBAL\Platforms\AbstractPlatform;
20
use Doctrine\DBAL\Types\Type;
21
use Doctrine\ORM\EntityManager;
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\OptimisticLockException;
24
use Doctrine\ORM\Query;
25
use Doctrine\ORM\QueryBuilder;
26
use Doctrine\ORM\UnitOfWork;
27
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
28
use Sonata\AdminBundle\Datagrid\DatagridInterface;
29
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
30
use Sonata\AdminBundle\Exception\LockException;
31
use Sonata\AdminBundle\Exception\ModelManagerException;
32
use Sonata\AdminBundle\Model\LockInterface;
33
use Sonata\AdminBundle\Model\ModelManagerInterface;
34
use Sonata\DoctrineORMAdminBundle\Admin\FieldDescription;
35
use Sonata\DoctrineORMAdminBundle\Datagrid\OrderByToSelectWalker;
36
use Sonata\DoctrineORMAdminBundle\Datagrid\ProxyQuery;
37
use Sonata\Exporter\Source\DoctrineORMQuerySourceIterator;
38
use Symfony\Bridge\Doctrine\RegistryInterface;
39
use Symfony\Component\Form\Exception\PropertyAccessDeniedException;
40
41
class ModelManager implements ModelManagerInterface, LockInterface
42
{
43
    public const ID_SEPARATOR = '~';
44
    /**
45
     * @var RegistryInterface
46
     */
47
    protected $registry;
48
49
    /**
50
     * @var EntityManager[]
51
     */
52
    protected $cache = [];
53
54
    public function __construct(RegistryInterface $registry)
55
    {
56
        $this->registry = $registry;
57
    }
58
59
    /**
60
     * @param string $class
61
     *
62
     * @return ClassMetadata
63
     */
64
    public function getMetadata($class)
65
    {
66
        return $this->getEntityManager($class)->getMetadataFactory()->getMetadataFor($class);
67
    }
68
69
    /**
70
     * Returns the model's metadata holding the fully qualified property, and the last
71
     * property name.
72
     *
73
     * @param string $baseClass        The base class of the model holding the fully qualified property
74
     * @param string $propertyFullName The name of the fully qualified property (dot ('.') separated
75
     *                                 property string)
76
     *
77
     * @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...
78
     *                \Doctrine\ORM\Mapping\ClassMetadata $parentMetadata,
79
     *                string $lastPropertyName,
80
     *                array $parentAssociationMappings
81
     *                )
82
     */
83
    public function getParentMetadataForProperty($baseClass, $propertyFullName)
84
    {
85
        $nameElements = explode('.', $propertyFullName);
86
        $lastPropertyName = array_pop($nameElements);
87
        $class = $baseClass;
88
        $parentAssociationMappings = [];
89
90
        foreach ($nameElements as $nameElement) {
91
            $metadata = $this->getMetadata($class);
92
93
            if (isset($metadata->associationMappings[$nameElement])) {
94
                $parentAssociationMappings[] = $metadata->associationMappings[$nameElement];
95
                $class = $metadata->getAssociationTargetClass($nameElement);
96
97
                continue;
98
            }
99
100
            break;
101
        }
102
103
        $properties = \array_slice($nameElements, \count($parentAssociationMappings));
104
        $properties[] = $lastPropertyName;
105
106
        return [$this->getMetadata($class), implode('.', $properties), $parentAssociationMappings];
107
    }
108
109
    /**
110
     * @param string $class
111
     *
112
     * @return bool
113
     */
114
    public function hasMetadata($class)
115
    {
116
        return $this->getEntityManager($class)->getMetadataFactory()->hasMetadataFor($class);
117
    }
118
119
    public function getNewFieldDescriptionInstance($class, $name, array $options = [])
120
    {
121
        if (!\is_string($name)) {
122
            throw new \RuntimeException('The name argument must be a string');
123
        }
124
125
        if (!isset($options['route']['name'])) {
126
            $options['route']['name'] = 'edit';
127
        }
128
129
        if (!isset($options['route']['parameters'])) {
130
            $options['route']['parameters'] = [];
131
        }
132
133
        list($metadata, $propertyName, $parentAssociationMappings) = $this->getParentMetadataForProperty($class, $name);
134
135
        $fieldDescription = new FieldDescription();
136
        $fieldDescription->setName($name);
137
        $fieldDescription->setOptions($options);
138
        $fieldDescription->setParentAssociationMappings($parentAssociationMappings);
139
140
        if (isset($metadata->associationMappings[$propertyName])) {
141
            $fieldDescription->setAssociationMapping($metadata->associationMappings[$propertyName]);
142
        }
143
144
        if (isset($metadata->fieldMappings[$propertyName])) {
145
            $fieldDescription->setFieldMapping($metadata->fieldMappings[$propertyName]);
146
        }
147
148
        return $fieldDescription;
149
    }
150
151
    public function create($object): void
152
    {
153
        try {
154
            $entityManager = $this->getEntityManager($object);
155
            $entityManager->persist($object);
156
            $entityManager->flush();
157
        } catch (\PDOException $e) {
158
            throw new ModelManagerException(
159
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
160
                $e->getCode(),
161
                $e
162
            );
163
        } catch (DBALException $e) {
164
            throw new ModelManagerException(
165
                sprintf('Failed to create object: %s', ClassUtils::getClass($object)),
166
                $e->getCode(),
167
                $e
168
            );
169
        }
170
    }
171
172
    public function update($object): void
173
    {
174
        try {
175
            $entityManager = $this->getEntityManager($object);
176
            $entityManager->persist($object);
177
            $entityManager->flush();
178
        } catch (\PDOException $e) {
179
            throw new ModelManagerException(
180
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
181
                $e->getCode(),
182
                $e
183
            );
184
        } catch (DBALException $e) {
185
            throw new ModelManagerException(
186
                sprintf('Failed to update object: %s', ClassUtils::getClass($object)),
187
                $e->getCode(),
188
                $e
189
            );
190
        }
191
    }
192
193
    public function delete($object): void
194
    {
195
        try {
196
            $entityManager = $this->getEntityManager($object);
197
            $entityManager->remove($object);
198
            $entityManager->flush();
199
        } catch (\PDOException $e) {
200
            throw new ModelManagerException(
201
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
202
                $e->getCode(),
203
                $e
204
            );
205
        } catch (DBALException $e) {
206
            throw new ModelManagerException(
207
                sprintf('Failed to delete object: %s', ClassUtils::getClass($object)),
208
                $e->getCode(),
209
                $e
210
            );
211
        }
212
    }
213
214
    public function getLockVersion($object)
215
    {
216
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
217
218
        if (!$metadata->isVersioned) {
219
            return;
220
        }
221
222
        return $metadata->reflFields[$metadata->versionField]->getValue($object);
223
    }
224
225
    public function lock($object, $expectedVersion): void
226
    {
227
        $metadata = $this->getMetadata(ClassUtils::getClass($object));
228
229
        if (!$metadata->isVersioned) {
230
            return;
231
        }
232
233
        try {
234
            $entityManager = $this->getEntityManager($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...
235
            $entityManager->lock($object, LockMode::OPTIMISTIC, $expectedVersion);
236
        } catch (OptimisticLockException $e) {
237
            throw new LockException($e->getMessage(), $e->getCode(), $e);
238
        }
239
    }
240
241
    public function find($class, $id)
242
    {
243
        if (!isset($id)) {
244
            return;
245
        }
246
247
        $values = array_combine($this->getIdentifierFieldNames($class), explode(self::ID_SEPARATOR, (string) $id));
248
249
        return $this->getEntityManager($class)->getRepository($class)->find($values);
250
    }
251
252
    public function findBy($class, array $criteria = [])
253
    {
254
        return $this->getEntityManager($class)->getRepository($class)->findBy($criteria);
255
    }
256
257
    public function findOneBy($class, array $criteria = [])
258
    {
259
        return $this->getEntityManager($class)->getRepository($class)->findOneBy($criteria);
260
    }
261
262
    /**
263
     * @param string $class
264
     *
265
     * @return EntityManager
266
     */
267
    public function getEntityManager($class)
268
    {
269
        if (\is_object($class)) {
270
            $class = \get_class($class);
271
        }
272
273
        if (!isset($this->cache[$class])) {
274
            $em = $this->registry->getManagerForClass($class);
275
276
            if (!$em) {
277
                throw new \RuntimeException(sprintf('No entity manager defined for class %s', $class));
278
            }
279
280
            $this->cache[$class] = $em;
281
        }
282
283
        return $this->cache[$class];
284
    }
285
286
    public function getParentFieldDescription($parentAssociationMapping, $class)
287
    {
288
        $fieldName = $parentAssociationMapping['fieldName'];
289
290
        $metadata = $this->getMetadata($class);
291
292
        $associatingMapping = $metadata->associationMappings[$parentAssociationMapping];
293
294
        $fieldDescription = $this->getNewFieldDescriptionInstance($class, $fieldName);
295
        $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...
296
        $fieldDescription->setAssociationMapping($associatingMapping);
297
298
        return $fieldDescription;
299
    }
300
301
    public function createQuery($class, $alias = 'o')
302
    {
303
        $repository = $this->getEntityManager($class)->getRepository($class);
304
305
        return new ProxyQuery($repository->createQueryBuilder($alias));
306
    }
307
308
    public function executeQuery($query)
309
    {
310
        if ($query instanceof QueryBuilder) {
311
            return $query->getQuery()->execute();
312
        }
313
314
        return $query->execute();
315
    }
316
317
    public function getModelIdentifier($class)
318
    {
319
        return $this->getMetadata($class)->identifier;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getMetadata($class)->identifier; (array) is incompatible with the return type declared by the interface Sonata\AdminBundle\Model...ace::getModelIdentifier of type string.

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...
320
    }
321
322
    public function getIdentifierValues($entity)
323
    {
324
        // Fix code has an impact on performance, so disable it ...
325
        //$entityManager = $this->getEntityManager($entity);
326
        //if (!$entityManager->getUnitOfWork()->isInIdentityMap($entity)) {
327
        //    throw new \RuntimeException('Entities passed to the choice field must be managed');
328
        //}
329
330
        $class = ClassUtils::getClass($entity);
331
        $metadata = $this->getMetadata($class);
332
        $platform = $this->getEntityManager($class)->getConnection()->getDatabasePlatform();
333
334
        $identifiers = [];
335
336
        foreach ($metadata->getIdentifierValues($entity) as $name => $value) {
337
            if (!\is_object($value)) {
338
                $identifiers[] = $value;
339
340
                continue;
341
            }
342
343
            $fieldType = $metadata->getTypeOfField($name);
344
            $type = $fieldType && Type::hasType($fieldType) ? Type::getType($fieldType) : null;
345
            if ($type) {
346
                $identifiers[] = $this->getValueFromType($value, $type, $fieldType, $platform);
347
348
                continue;
349
            }
350
351
            $metadata = $this->getMetadata(ClassUtils::getClass($value));
352
353
            foreach ($metadata->getIdentifierValues($value) as $value) {
354
                $identifiers[] = $value;
355
            }
356
        }
357
358
        return $identifiers;
359
    }
360
361
    public function getIdentifierFieldNames($class)
362
    {
363
        return $this->getMetadata($class)->getIdentifierFieldNames();
364
    }
365
366
    public function getNormalizedIdentifier($entity)
367
    {
368
        if (is_scalar($entity)) {
369
            throw new \RuntimeException('Invalid argument, object or null required');
370
        }
371
372
        if (!$entity) {
373
            return;
374
        }
375
376
        if (\in_array($this->getEntityManager($entity)->getUnitOfWork()->getEntityState($entity), [
0 ignored issues
show
Documentation introduced by
$entity 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...
377
            UnitOfWork::STATE_NEW,
378
            UnitOfWork::STATE_REMOVED,
379
        ], true)) {
380
            return;
381
        }
382
383
        $values = $this->getIdentifierValues($entity);
384
385
        if (0 === \count($values)) {
386
            return;
387
        }
388
389
        return implode(self::ID_SEPARATOR, $values);
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     *
395
     * The ORM implementation does nothing special but you still should use
396
     * this method when using the id in a URL to allow for future improvements.
397
     */
398
    public function getUrlsafeIdentifier($entity)
399
    {
400
        return $this->getNormalizedIdentifier($entity);
401
    }
402
403
    public function addIdentifiersToQuery($class, ProxyQueryInterface $queryProxy, array $idx): void
404
    {
405
        $fieldNames = $this->getIdentifierFieldNames($class);
406
        $qb = $queryProxy->getQueryBuilder();
407
408
        $prefix = uniqid();
409
        $sqls = [];
410
        foreach ($idx as $pos => $id) {
411
            $ids = explode(self::ID_SEPARATOR, $id);
412
413
            $ands = [];
414
            foreach ($fieldNames as $posName => $name) {
415
                $parameterName = sprintf('field_%s_%s_%d', $prefix, $name, $pos);
416
                $ands[] = sprintf('%s.%s = :%s', current($qb->getRootAliases()), $name, $parameterName);
417
                $qb->setParameter($parameterName, $ids[$posName]);
418
            }
419
420
            $sqls[] = implode(' AND ', $ands);
421
        }
422
423
        $qb->andWhere(sprintf('( %s )', implode(' OR ', $sqls)));
424
    }
425
426
    public function batchDelete($class, ProxyQueryInterface $queryProxy): void
427
    {
428
        $queryProxy->select('DISTINCT '.current($queryProxy->getRootAliases()));
429
430
        try {
431
            $entityManager = $this->getEntityManager($class);
432
433
            $i = 0;
434
            foreach ($queryProxy->getQuery()->iterate() as $pos => $object) {
435
                $entityManager->remove($object[0]);
436
437
                if (0 === (++$i % 20)) {
438
                    $entityManager->flush();
439
                    $entityManager->clear();
440
                }
441
            }
442
443
            $entityManager->flush();
444
            $entityManager->clear();
445
        } catch (\PDOException $e) {
446
            throw new ModelManagerException('', 0, $e);
447
        } catch (DBALException $e) {
448
            throw new ModelManagerException('', 0, $e);
449
        }
450
    }
451
452
    public function getDataSourceIterator(DatagridInterface $datagrid, array $fields, $firstResult = null, $maxResult = null)
453
    {
454
        $datagrid->buildPager();
455
        $query = $datagrid->getQuery();
456
457
        $query->select('DISTINCT '.current($query->getRootAliases()));
458
        $query->setFirstResult($firstResult);
459
        $query->setMaxResults($maxResult);
460
461
        if ($query instanceof ProxyQueryInterface) {
462
            $sortBy = $query->getSortBy();
463
464
            if (!empty($sortBy)) {
465
                $query->addOrderBy($sortBy, $query->getSortOrder());
466
                $query = $query->getQuery();
467
                $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, [OrderByToSelectWalker::class]);
468
            } else {
469
                $query = $query->getQuery();
470
            }
471
        }
472
473
        return new DoctrineORMQuerySourceIterator($query, $fields);
474
    }
475
476
    public function getExportFields($class)
477
    {
478
        $metadata = $this->getEntityManager($class)->getClassMetadata($class);
479
480
        return $metadata->getFieldNames();
481
    }
482
483
    public function getModelInstance($class)
484
    {
485
        $r = new \ReflectionClass($class);
486
        if ($r->isAbstract()) {
487
            throw new \RuntimeException(sprintf('Cannot initialize abstract class: %s', $class));
488
        }
489
490
        $constructor = $r->getConstructor();
491
492
        if (null !== $constructor && (!$constructor->isPublic() || $constructor->getNumberOfRequiredParameters() > 0)) {
493
            return $r->newInstanceWithoutConstructor();
494
        }
495
496
        return new $class();
497
    }
498
499
    public function getSortParameters(FieldDescriptionInterface $fieldDescription, DatagridInterface $datagrid)
500
    {
501
        $values = $datagrid->getValues();
502
503
        if ($fieldDescription->getName() === $values['_sort_by']->getName() || $values['_sort_by']->getName() === $fieldDescription->getOption('sortable')) {
504
            if ('ASC' === $values['_sort_order']) {
505
                $values['_sort_order'] = 'DESC';
506
            } else {
507
                $values['_sort_order'] = 'ASC';
508
            }
509
        } else {
510
            $values['_sort_order'] = 'ASC';
511
        }
512
513
        $values['_sort_by'] = \is_string($fieldDescription->getOption('sortable')) ? $fieldDescription->getOption('sortable') : $fieldDescription->getName();
514
515
        return ['filter' => $values];
516
    }
517
518
    public function getPaginationParameters(DatagridInterface $datagrid, $page)
519
    {
520
        $values = $datagrid->getValues();
521
522
        $values['_sort_by'] = $values['_sort_by']->getName();
523
        $values['_page'] = $page;
524
525
        return ['filter' => $values];
526
    }
527
528
    public function getDefaultSortValues($class)
529
    {
530
        return [
531
            '_sort_order' => 'ASC',
532
            '_sort_by' => implode(',', $this->getModelIdentifier($class)),
533
            '_page' => 1,
534
            '_per_page' => 25,
535
        ];
536
    }
537
538
    public function modelTransform($class, $instance)
539
    {
540
        return $instance;
541
    }
542
543
    public function modelReverseTransform($class, array $array = [])
544
    {
545
        $instance = $this->getModelInstance($class);
546
        $metadata = $this->getMetadata($class);
547
548
        $reflClass = $metadata->reflClass;
549
        foreach ($array as $name => $value) {
550
            $reflection_property = false;
551
            // property or association ?
552
            if (\array_key_exists($name, $metadata->fieldMappings)) {
553
                $property = $metadata->fieldMappings[$name]['fieldName'];
554
                $reflection_property = $metadata->reflFields[$name];
555
            } elseif (\array_key_exists($name, $metadata->associationMappings)) {
556
                $property = $metadata->associationMappings[$name]['fieldName'];
557
            } else {
558
                $property = $name;
559
            }
560
561
            $setter = 'set'.$this->camelize($name);
562
563
            if ($reflClass->hasMethod($setter)) {
564
                if (!$reflClass->getMethod($setter)->isPublic()) {
565
                    throw new PropertyAccessDeniedException(sprintf(
566
                        'Method "%s()" is not public in class "%s"',
567
                        $setter,
568
                        $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...
569
                    ));
570
                }
571
572
                $instance->$setter($value);
573
            } elseif ($reflClass->hasMethod('__set')) {
574
                // needed to support magic method __set
575
                $instance->$property = $value;
576
            } elseif ($reflClass->hasProperty($property)) {
577
                if (!$reflClass->getProperty($property)->isPublic()) {
578
                    throw new PropertyAccessDeniedException(sprintf(
579
                        'Property "%s" is not public in class "%s". Maybe you should create the method "set%s()"?',
580
                            $property,
581
                            $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...
582
                            ucfirst($property)
583
                    ));
584
                }
585
586
                $instance->$property = $value;
587
            } elseif ($reflection_property) {
588
                $reflection_property->setValue($instance, $value);
589
            }
590
        }
591
592
        return $instance;
593
    }
594
595
    public function getModelCollectionInstance($class)
596
    {
597
        return new \Doctrine\Common\Collections\ArrayCollection();
598
    }
599
600
    public function collectionClear(&$collection)
601
    {
602
        return $collection->clear();
603
    }
604
605
    public function collectionHasElement(&$collection, &$element)
606
    {
607
        return $collection->contains($element);
608
    }
609
610
    public function collectionAddElement(&$collection, &$element)
611
    {
612
        return $collection->add($element);
613
    }
614
615
    public function collectionRemoveElement(&$collection, &$element)
616
    {
617
        return $collection->removeElement($element);
618
    }
619
620
    /**
621
     * method taken from Symfony\Component\PropertyAccess\PropertyAccessor.
622
     *
623
     * @param string $property
624
     *
625
     * @return mixed
626
     */
627
    protected function camelize($property)
628
    {
629
        return str_replace(' ', '', ucwords(str_replace('_', ' ', $property)));
630
    }
631
632
    /**
633
     * @param mixed $value
634
     */
635
    private function getValueFromType($value, Type $type, string $fieldType, AbstractPlatform $platform): string
636
    {
637
        if ('binary' === $platform->getDoctrineTypeMapping($fieldType)) {
638
            return (string) $type->convertToPHPValue($value, $platform);
639
        }
640
641
        return $type->convertToDatabaseValue($value, $platform);
642
    }
643
}
644