FormContractor::fixFieldDescription()   B
last analyzed

Complexity

Conditions 6
Paths 15

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.8017
c 0
b 0
f 0
cc 6
nc 15
nop 2
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\DoctrineMongoDBAdminBundle\Builder;
15
16
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
17
use Sonata\AdminBundle\Admin\AdminInterface;
18
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
19
use Sonata\AdminBundle\Builder\FormContractorInterface;
20
use Sonata\AdminBundle\Form\Type\AdminType;
21
use Sonata\AdminBundle\Form\Type\ModelAutocompleteType;
22
use Sonata\AdminBundle\Form\Type\ModelHiddenType;
23
use Sonata\AdminBundle\Form\Type\ModelListType;
24
use Sonata\AdminBundle\Form\Type\ModelType;
25
use Sonata\AdminBundle\Form\Type\ModelTypeList;
26
use Sonata\Form\Type\CollectionType;
27
use Symfony\Component\Form\Extension\Core\Type\FormType;
28
use Symfony\Component\Form\FormFactoryInterface;
29
30
class FormContractor implements FormContractorInterface
31
{
32
    /**
33
     * @deprecated since version 3.1.0, to be removed in 4.0
34
     *
35
     * @var FormFactoryInterface
36
     */
37
    protected $fieldFactory;
38
39
    /**
40
     * @var FormFactoryInterface
41
     */
42
    private $formFactory;
43
44
    public function __construct(FormFactoryInterface $formFactory)
45
    {
46
        $this->formFactory = $formFactory;
47
    }
48
49
    public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription): void
50
    {
51
        if ($admin->getModelManager()->hasMetadata($admin->getClass())) {
52
            $metadata = $admin->getModelManager()->getMetadata($admin->getClass());
53
54
            // set the default field mapping
55
            if (isset($metadata->fieldMappings[$fieldDescription->getName()])) {
56
                $fieldDescription->setFieldMapping($metadata->fieldMappings[$fieldDescription->getName()]);
57
            }
58
59
            // set the default association mapping
60
            if (isset($metadata->associationMappings[$fieldDescription->getName()])) {
61
                $fieldDescription->setAssociationMapping($metadata->associationMappings[$fieldDescription->getName()]);
62
            }
63
        }
64
65
        if (!$fieldDescription->getType()) {
66
            throw new \RuntimeException(sprintf(
67
                'Please define a type for field `%s` in `%s`',
68
                $fieldDescription->getName(),
69
                \get_class($admin)
70
            ));
71
        }
72
73
        $fieldDescription->setAdmin($admin);
74
        $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'standard'));
75
76
        if (\in_array($fieldDescription->getMappingType(), [ClassMetadata::ONE, ClassMetadata::MANY], true)) {
77
            $admin->attachAdminClass($fieldDescription);
78
        }
79
    }
80
81
    /**
82
     * @return FormFactoryInterface
83
     */
84
    public function getFormFactory()
85
    {
86
        return $this->formFactory;
87
    }
88
89
    public function getFormBuilder($name, array $options = [])
90
    {
91
        return $this->getFormFactory()->createNamedBuilder($name, FormType::class, null, $options);
92
    }
93
94
    public function getDefaultOptions($type, FieldDescriptionInterface $fieldDescription)
95
    {
96
        $options = [];
97
        $options['sonata_field_description'] = $fieldDescription;
98
99
        if ($this->checkFormClass($type, [
100
            ModelType::class,
101
            ModelTypeList::class,
102
            ModelListType::class,
103
            ModelHiddenType::class,
104
            ModelAutocompleteType::class,
105
        ])) {
106
            if ('list' === $fieldDescription->getOption('edit')) {
107
                throw new \LogicException(
108
                    'The ``Sonata\AdminBundle\Form\Type\ModelType`` type does not accept an ``edit`` option anymore,'
109
                    .' please review the UPGRADE-2.1.md file from the SonataAdminBundle'
110
                );
111
            }
112
113
            $options['class'] = $fieldDescription->getTargetModel();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\DoctrineMongoDBAd...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
114
            $options['model_manager'] = $fieldDescription->getAdmin()->getModelManager();
115
116
            if ($this->checkFormClass($type, [ModelAutocompleteType::class])) {
117
                if (!$fieldDescription->getAssociationAdmin()) {
118
                    throw new \RuntimeException(sprintf(
119
                        'The current field `%s` is not linked to an admin.'
120
                        .' Please create one for the target entity: `%s`',
121
                        $fieldDescription->getName(),
122
                        $fieldDescription->getTargetModel()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\DoctrineMongoDBAd...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
123
                    ));
124
                }
125
            }
126
        } elseif ($this->checkFormClass($type, [AdminType::class])) {
127
            if (!$fieldDescription->getAssociationAdmin()) {
128
                throw new \RuntimeException(sprintf(
129
                    'The current field `%s` is not linked to an admin.'
130
                    .' Please create one for the target entity : `%s`',
131
                    $fieldDescription->getName(),
132
                    $fieldDescription->getTargetModel()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\DoctrineMongoDBAd...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
133
                ));
134
            }
135
136
            if (!\in_array($fieldDescription->getMappingType(), [ClassMetadata::ONE, ClassMetadata::MANY], true)) {
137
                throw new \RuntimeException(sprintf(
138
                    'You are trying to add `%s` field `%s` which is not One-To-One or Many-To-One.'
139
                    .' Maybe you want `%s` instead?',
140
                    AdminType::class,
141
                    $fieldDescription->getName(),
142
                    CollectionType::class
143
                ));
144
            }
145
146
            // set sensitive default value to have a component working fine out of the box
147
            $options['btn_add'] = false;
148
            $options['delete'] = false;
149
150
            $options['data_class'] = $fieldDescription->getAssociationAdmin()->getClass();
151
            $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'admin'));
152
        } elseif ($this->checkFormClass($type, [
153
            CollectionType::class,
154
        ])) {
155
            if (!$fieldDescription->getAssociationAdmin()) {
156
                throw new \RuntimeException(sprintf(
157
                    'The current field `%s` is not linked to an admin.'
158
                    .' Please create one for the target entity : `%s`',
159
                    $fieldDescription->getName(),
160
                    $fieldDescription->getTargetModel()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\AdminBundle\Admin\FieldDescriptionInterface as the method getTargetModel() does only exist in the following implementations of said interface: Sonata\DoctrineMongoDBAd...\Admin\FieldDescription.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
161
                ));
162
            }
163
164
            $options['type'] = AdminType::class;
165
            $options['modifiable'] = true;
166
            $options['type_options'] = [
167
                'sonata_field_description' => $fieldDescription,
168
                'data_class' => $fieldDescription->getAssociationAdmin()->getClass(),
169
            ];
170
        }
171
172
        return $options;
173
    }
174
175
    /**
176
     * @param string $type
177
     * @param array  $classes
178
     *
179
     * @return array
180
     */
181
    private function checkFormClass($type, $classes)
182
    {
183
        return array_filter($classes, static function ($subclass) use ($type) {
184
            return is_a($type, $subclass, true);
185
        });
186
    }
187
}
188