Completed
Pull Request — 3.x (#5937)
by Peter
05:42 queued 01:36
created

DataTransformerResolverTest::setUp()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\AdminBundle\Tests\Form;
15
16
use PHPUnit\Framework\TestCase;
17
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
18
use Sonata\AdminBundle\Form\DataTransformer\BooleanToStringTransformer;
19
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
20
use Sonata\AdminBundle\Form\DataTransformerResolver;
21
use Sonata\AdminBundle\Model\ModelManagerInterface;
22
use Symfony\Component\Form\CallbackTransformer;
23
use Symfony\Component\Form\DataTransformerInterface;
24
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
25
26
/**
27
 * @author Peter Gribanov <[email protected]>
28
 */
29
final class DataTransformerResolverTest extends TestCase
30
{
31
    /**
32
     * @var DataTransformerResolver
33
     */
34
    private $resolver;
35
36
    /**
37
     * @var FieldDescriptionInterface
38
     */
39
    private $fieldDescription;
40
41
    /**
42
     * @var FieldDescriptionInterface
43
     */
44
    private $modelManager;
45
46
    protected function setUp(): void
47
    {
48
        $this->fieldDescription = $this->prophesize(FieldDescriptionInterface::class);
49
        $this->modelManager = $this->prophesize(ModelManagerInterface::class);
50
        $this->resolver = new DataTransformerResolver();
51
    }
52
53
    public function testFailedResolve(): void
54
    {
55
        $this->assertNull($this->resolve());
56
    }
57
58
    public function provideFieldTypes(): array
59
    {
60
        return [
61
            ['foo'],
62
            // override predefined transformers
63
            ['date'],
64
            ['boolean'],
65
            ['choice'],
66
        ];
67
    }
68
69
    /**
70
     * @dataProvider provideFieldTypes
71
     */
72
    public function testResolveCustomDataTransformer(string $fieldType): void
73
    {
74
        $customDataTransformer = new CallbackTransformer(static function ($value): string {
75
            return (string) (int) $value;
76
        }, static function ($value): bool {
77
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
78
        });
79
        $this->fieldDescription->getOption('data_transformer')->willReturn($customDataTransformer);
80
        $this->fieldDescription->getType()->willReturn($fieldType);
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
81
82
        $dataTransformer = $this->resolve();
83
84
        $this->assertInstanceOf(DataTransformerInterface::class, $dataTransformer);
85
        $this->assertSame($customDataTransformer, $dataTransformer);
86
    }
87
88
    public function testResolveDateDataTransformer(): void
89
    {
90
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
91
        $this->fieldDescription->getType()->willReturn('date');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
92
93
        $dataTransformer = $this->resolve();
94
95
        $this->assertInstanceOf(DateTimeToStringTransformer::class, $dataTransformer);
96
97
        // test laze-load
98
        $secondDataTransformer = $this->resolve();
99
100
        $this->assertSame($dataTransformer, $secondDataTransformer);
101
    }
102
103
    public function testResolveDateDatatimeTransformer(): void
104
    {
105
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
106
        $this->fieldDescription->getType()->willReturn('datetime');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
107
108
        $dataTransformer = $this->resolve();
109
110
        $this->assertInstanceOf(DateTimeToStringTransformer::class, $dataTransformer);
111
112
        // test laze-load
113
        $secondDataTransformer = $this->resolve();
114
115
        $this->assertSame($dataTransformer, $secondDataTransformer);
116
    }
117
118
    public function testResolveBooleanDataTransformer(): void
119
    {
120
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
121
        $this->fieldDescription->getType()->willReturn('boolean');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
122
123
        $dataTransformer = $this->resolve();
124
125
        $this->assertInstanceOf(BooleanToStringTransformer::class, $dataTransformer);
126
        $this->assertSame('1', $dataTransformer->transform(true));
127
128
        // test laze-load
129
        $secondDataTransformer = $this->resolve();
130
131
        $this->assertSame($dataTransformer, $secondDataTransformer);
132
    }
133
134
    public function testResolveChoiceWithoutClassName(): void
135
    {
136
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
137
        $this->fieldDescription->getType()->willReturn('choice');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
138
        $this->fieldDescription->getOption('class')->willReturn(null);
139
140
        $this->assertNull($this->resolve());
141
    }
142
143
    public function testResolveChoiceBadClassName(): void
144
    {
145
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
146
        $this->fieldDescription->getType()->willReturn('choice');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
147
        $this->fieldDescription->getOption('class')->willReturn(\stdClass::class);
148
        $this->fieldDescription->getTargetModel()->willReturn(\DateTime::class);
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\AdminBundle\Tests...\Admin\FieldDescription, Sonata\AdminBundle\Tests...\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...
149
        $this->fieldDescription->getTargetEntity()->willReturn(\DateTime::class);
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getTargetEntity() (of type string|null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Deprecated Code introduced by
The method Sonata\AdminBundle\Admin...face::getTargetEntity() has been deprecated with message: since sonata-project/admin-bundle 3.69. Use `getTargetModel()` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
150
151
        $this->assertNull($this->resolve());
152
    }
153
154
    public function testResolveChoice(): void
155
    {
156
        $newId = 1;
157
        $className = \stdClass::class;
158
        $object = new \stdClass();
159
160
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
161
        $this->fieldDescription->getType()->willReturn('choice');
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
162
        $this->fieldDescription->getOption('class')->willReturn($className);
163
        $this->fieldDescription->getTargetModel()->willReturn($className);
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\AdminBundle\Tests...\Admin\FieldDescription, Sonata\AdminBundle\Tests...\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...
164
165
        $this->modelManager->find($className, $newId)->willReturn($object);
0 ignored issues
show
Bug introduced by
The method find() does not seem to exist on object<Sonata\AdminBundl...ldDescriptionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
166
167
        $dataTransformer = $this->resolve();
168
169
        $this->assertInstanceOf(ModelToIdTransformer::class, $dataTransformer);
170
        $this->assertSame($object, $dataTransformer->reverseTransform($newId));
171
    }
172
173
    /**
174
     * @dataProvider provideFieldTypes
175
     */
176
    public function testCustomGlobalTransformers(string $fieldType): void
177
    {
178
        $customDataTransformer = new CallbackTransformer(static function ($value): string {
179
            return (string) (int) $value;
180
        }, static function ($value): bool {
181
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
182
        });
183
184
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
185
        $this->fieldDescription->getType()->willReturn($fieldType);
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
186
187
        $this->resolver = new DataTransformerResolver([
188
            $fieldType => $customDataTransformer, // override predefined transformer
189
        ]);
190
191
        $dataTransformer = $this->resolve();
192
193
        $this->assertInstanceOf(DataTransformerInterface::class, $dataTransformer);
194
        $this->assertSame($customDataTransformer, $dataTransformer);
195
    }
196
197
    /**
198
     * @dataProvider provideFieldTypes
199
     */
200
    public function testAddCustomGlobalTransformer(string $fieldType): void
201
    {
202
        $customDataTransformer = new CallbackTransformer(static function ($value): string {
203
            return (string) (int) $value;
204
        }, static function ($value): bool {
205
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
206
        });
207
208
        $this->fieldDescription->getOption('data_transformer')->willReturn(null);
209
        $this->fieldDescription->getType()->willReturn($fieldType);
0 ignored issues
show
Bug introduced by
The method willReturn cannot be called on $this->fieldDescription->getType() (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
210
211
        $this->resolver->addCustomGlobalTransformer($fieldType, $customDataTransformer);
212
213
        $dataTransformer = $this->resolve();
214
215
        $this->assertInstanceOf(DataTransformerInterface::class, $dataTransformer);
216
        $this->assertSame($customDataTransformer, $dataTransformer);
217
    }
218
219
    protected function resolve(): ?DataTransformerInterface
220
    {
221
        return $this->resolver->resolve($this->fieldDescription->reveal(), $this->modelManager->reveal());
0 ignored issues
show
Bug introduced by
The method reveal() does not seem to exist on object<Sonata\AdminBundl...ldDescriptionInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
    }
223
}
224