Completed
Push — master ( c17fc1...538d13 )
by
unknown
01:51 queued 12s
created

FormContractor   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 180
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 3
dl 0
loc 180
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B fixFieldDescription() 0 31 7
A getFormFactory() 0 4 1
A getFormBuilder() 0 4 1
A hasAssociation() 0 9 1
C getDefaultOptions() 0 89 10
A isAnyInstanceOf() 0 10 3
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\Builder;
15
16
use Doctrine\ORM\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
     * NEXT_MAJOR: remove this property.
34
     *
35
     * @deprecated since sonata-project/doctrine-orm-admin-bundle 3.0.4, to be removed in 4.0
36
     *
37
     * @var FormFactoryInterface
38
     */
39
    protected $fieldFactory;
40
41
    /**
42
     * @var FormFactoryInterface
43
     */
44
    protected $formFactory;
45
46
    public function __construct(FormFactoryInterface $formFactory)
47
    {
48
        $this->formFactory = $formFactory;
49
    }
50
51
    public function fixFieldDescription(AdminInterface $admin, FieldDescriptionInterface $fieldDescription): void
52
    {
53
        if ($admin->getModelManager()->hasMetadata($admin->getClass())) {
54
            $metadata = $admin->getModelManager()->getMetadata($admin->getClass());
55
56
            // set the default field mapping
57
            if (isset($metadata->fieldMappings[$fieldDescription->getName()])) {
58
                $fieldDescription->setFieldMapping($metadata->fieldMappings[$fieldDescription->getName()]);
59
            }
60
61
            // set the default association mapping
62
            if (isset($metadata->associationMappings[$fieldDescription->getName()])) {
63
                $fieldDescription->setAssociationMapping($metadata->associationMappings[$fieldDescription->getName()]);
64
            }
65
        }
66
67
        if (!$fieldDescription->getType()) {
68
            throw new \RuntimeException(sprintf(
69
                'Please define a type for field `%s` in `%s`',
70
                $fieldDescription->getName(),
71
                \get_class($admin)
72
            ));
73
        }
74
75
        $fieldDescription->setAdmin($admin);
76
        $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'standard'));
77
78
        if ($this->hasAssociation($fieldDescription) || $fieldDescription->getOption('admin_code')) {
79
            $admin->attachAdminClass($fieldDescription);
80
        }
81
    }
82
83
    /**
84
     * @return FormFactoryInterface
85
     */
86
    public function getFormFactory()
87
    {
88
        return $this->formFactory;
89
    }
90
91
    public function getFormBuilder($name, array $options = [])
92
    {
93
        return $this->getFormFactory()->createNamedBuilder($name, FormType::class, null, $options);
94
    }
95
96
    public function getDefaultOptions($type, FieldDescriptionInterface $fieldDescription)
97
    {
98
        $options = [];
99
        $options['sonata_field_description'] = $fieldDescription;
100
101
        if ($this->isAnyInstanceOf($type, [
102
            ModelType::class,
103
            ModelTypeList::class,
104
            ModelListType::class,
105
            ModelHiddenType::class,
106
            ModelAutocompleteType::class,
107
        ])) {
108
            if ('list' === $fieldDescription->getOption('edit')) {
109
                throw new \LogicException(sprintf(
110
                    'The `%s` type does not accept an `edit` option anymore,'
111
                    .' please review the UPGRADE-2.1.md file from the SonataAdminBundle',
112
                    ModelType::class
113
                ));
114
            }
115
116
            $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\DoctrineORMAdminB...\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...
117
            $options['model_manager'] = $fieldDescription->getAdmin()->getModelManager();
118
119
            if ($this->isAnyInstanceOf($type, [ModelAutocompleteType::class])) {
120
                if (!$fieldDescription->getAssociationAdmin()) {
121
                    throw new \RuntimeException(sprintf(
122
                        'The current field `%s` is not linked to an admin.'
123
                        .' Please create one for the target entity: `%s`',
124
                        $fieldDescription->getName(),
125
                        $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\DoctrineORMAdminB...\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...
126
                    ));
127
                }
128
            }
129
        } elseif ($this->isAnyInstanceOf($type, [AdminType::class])) {
130
            if (!$fieldDescription->getAssociationAdmin()) {
131
                throw new \RuntimeException(sprintf(
132
                    'The current field `%s` is not linked to an admin.'
133
                    .' Please create one for the target entity : `%s`',
134
                    $fieldDescription->getName(),
135
                    $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\DoctrineORMAdminB...\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...
136
                ));
137
            }
138
139
            if (!\in_array($fieldDescription->getMappingType(), [
140
                ClassMetadata::ONE_TO_ONE,
141
                ClassMetadata::MANY_TO_ONE,
142
            ], true)) {
143
                throw new \RuntimeException(sprintf(
144
                    'You are trying to add `%s` field `%s` which is not One-To-One or  Many-To-One.'
145
                    .' Maybe you want `%s` instead?',
146
                    AdminType::class,
147
                    $fieldDescription->getName(),
148
                    CollectionType::class
149
                ));
150
            }
151
152
            // set sensitive default value to have a component working fine out of the box
153
            $options['btn_add'] = false;
154
            $options['delete'] = false;
155
156
            $options['data_class'] = $fieldDescription->getAssociationAdmin()->getClass();
157
            $options['empty_data'] = static function () use ($fieldDescription) {
158
                return $fieldDescription->getAssociationAdmin()->getNewInstance();
159
            };
160
            $fieldDescription->setOption('edit', $fieldDescription->getOption('edit', 'admin'));
161
        // NEXT_MAJOR: remove 'Sonata\CoreBundle\Form\Type\CollectionType'
162
        } elseif ($this->isAnyInstanceOf($type, [CollectionType::class, 'Sonata\CoreBundle\Form\Type\CollectionType'])) {
163
            if (!$fieldDescription->getAssociationAdmin()) {
164
                throw new \RuntimeException(sprintf(
165
                    'The current field `%s` is not linked to an admin.'
166
                    .' Please create one for the target entity : `%s`',
167
                    $fieldDescription->getName(),
168
                    $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\DoctrineORMAdminB...\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...
169
                ));
170
            }
171
172
            $options['type'] = AdminType::class;
173
            $options['modifiable'] = true;
174
            $options['type_options'] = [
175
                'sonata_field_description' => $fieldDescription,
176
                'data_class' => $fieldDescription->getAssociationAdmin()->getClass(),
177
                'empty_data' => static function () use ($fieldDescription) {
178
                    return $fieldDescription->getAssociationAdmin()->getNewInstance();
179
                },
180
            ];
181
        }
182
183
        return $options;
184
    }
185
186
    private function hasAssociation(FieldDescriptionInterface $fieldDescription): bool
187
    {
188
        return \in_array($fieldDescription->getMappingType(), [
189
            ClassMetadata::ONE_TO_MANY,
190
            ClassMetadata::MANY_TO_MANY,
191
            ClassMetadata::MANY_TO_ONE,
192
            ClassMetadata::ONE_TO_ONE,
193
        ], true);
194
    }
195
196
    /**
197
     * @param string[] $classes
198
     */
199
    private function isAnyInstanceOf(string $type, array $classes): bool
200
    {
201
        foreach ($classes as $class) {
202
            if (is_a($type, $class, true)) {
203
                return true;
204
            }
205
        }
206
207
        return false;
208
    }
209
}
210