Completed
Push — feature/improve-form-object ( f89bd2...6080f1 )
by Romain
03:16
created

FormObjectFactory::getCacheInstance()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
/*
3
 * 2017 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 FormZ project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Form\FormObject;
15
16
use Romm\ConfigurationObject\ConfigurationObjectInterface;
17
use Romm\Formz\Configuration\Configuration;
18
use Romm\Formz\Configuration\ConfigurationFactory;
19
use Romm\Formz\Core\Core;
20
use Romm\Formz\Exceptions\ClassNotFoundException;
21
use Romm\Formz\Exceptions\DuplicateEntryException;
22
use Romm\Formz\Exceptions\EntryNotFoundException;
23
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
24
use Romm\Formz\Exceptions\InvalidArgumentValueException;
25
use Romm\Formz\Form\FormInterface;
26
use Romm\Formz\Form\FormObject\Builder\DefaultFormObjectBuilder;
27
use Romm\Formz\Form\FormObject\Builder\FormObjectBuilderInterface;
28
use Romm\Formz\Service\CacheService;
29
use Romm\Formz\Service\StringService;
30
use Romm\Formz\Service\Traits\ExtendedSelfInstantiateTrait;
31
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
32
use TYPO3\CMS\Core\SingletonInterface;
33
34
/**
35
 * Factory class that will manage form object instances.
36
 *
37
 * You can fetch a new instance by calling one of the following methods:
38
 * `getInstanceFromFormInstance()` or `getInstanceFromClassName()`
39
 *
40
 * @see \Romm\Formz\Form\FormObject\FormObject
41
 */
42
class FormObjectFactory implements SingletonInterface
43
{
44
    use ExtendedSelfInstantiateTrait;
45
46
    /**
47
     * @var FormObject[]
48
     */
49
    protected $instances = [];
50
51
    /**
52
     * @var FormObjectStatic[]
53
     */
54
    protected $static = [];
55
56
    /**
57
     * @var FormObjectProxy[]
58
     */
59
    protected $proxy = [];
60
61
    /**
62
     * Returns the form object for the given form instance. The form instance
63
     * must have been defined first in this factory, or an exception is thrown.
64
     *
65
     * @param FormInterface $form
66
     * @return FormObject
67
     * @throws EntryNotFoundException
68
     */
69
    public function getInstanceWithFormInstance(FormInterface $form)
70
    {
71
        if (false === $this->formInstanceWasRegistered($form)) {
72
            throw EntryNotFoundException::formObjectInstanceNotFound($form);
73
        }
74
75
        return $this->instances[spl_object_hash($form)];
76
    }
77
78
    /**
79
     * Checks that the given form instance was registered in this factory.
80
     *
81
     * @param FormInterface $form
82
     * @return bool
83
     */
84
    public function formInstanceWasRegistered(FormInterface $form)
85
    {
86
        return isset($this->instances[spl_object_hash($form)]);
87
    }
88
89
    /**
90
     * Registers a new form instance.
91
     *
92
     * @param FormInterface $form
93
     * @param string        $name
94
     * @throws DuplicateEntryException
95
     * @throws InvalidArgumentValueException
96
     */
97
    public function registerFormInstance(FormInterface $form, $name)
98
    {
99
        if (empty($name)) {
100
            throw InvalidArgumentValueException::formNameEmpty($form);
101
        }
102
103
        if ($this->formInstanceWasRegistered($form)) {
104
            throw DuplicateEntryException::formObjectInstanceAlreadyRegistered($form, $name);
105
        }
106
107
        $hash = spl_object_hash($form);
108
        $this->instances[$hash] = $this->getInstanceWithClassName(get_class($form), $name);
109
        $this->instances[$hash]->setForm($form);
110
    }
111
112
    /**
113
     * A shortcut function to register the given form instance (if it was not
114
     * already registered) and return the form object.
115
     *
116
     * @param FormInterface $form
117
     * @param string        $name
118
     * @return FormObject
119
     */
120
    public function registerAndGetFormInstance(FormInterface $form, $name)
121
    {
122
        if (false === $this->formInstanceWasRegistered($form)) {
123
            $this->registerFormInstance($form, $name);
124
        }
125
126
        return $this->getInstanceWithFormInstance($form);
127
    }
128
129
    /**
130
     * Will create an instance of `FormObject` based on a class that implements
131
     * the interface `FormInterface`.
132
     *
133
     * @param string $className
134
     * @param string $name
135
     * @return FormObject
136
     */
137
    public function getInstanceWithClassName($className, $name)
138
    {
139
        /** @var FormObject $formObject */
140
        $formObject = Core::instantiate(FormObject::class, $name, $this->getStaticInstance($className));
141
142
        return $formObject;
143
    }
144
145
    /**
146
     * Returns the proxy object for the given form object and form instance.
147
     *
148
     * Please use with caution, as this is a very low level function!
149
     *
150
     * @param FormInterface $form
151
     * @return FormObjectProxy
152
     */
153
    public function getProxy(FormInterface $form)
154
    {
155
        $hash = spl_object_hash($form);
156
157
        if (false === isset($this->proxy[$hash])) {
158
            $this->proxy[$hash] = $this->getNewProxyInstance($form);
159
        }
160
161
        return $this->proxy[$hash];
162
    }
163
164
    /**
165
     * @param string $className
166
     * @return FormObjectStatic
167
     * @throws ClassNotFoundException
168
     * @throws InvalidArgumentTypeException
169
     */
170
    protected function getStaticInstance($className)
171
    {
172
        if (false === class_exists($className)) {
173
            throw ClassNotFoundException::wrongFormClassName($className);
174
        }
175
176
        if (false === in_array(FormInterface::class, class_implements($className))) {
177
            throw InvalidArgumentTypeException::wrongFormType($className);
178
        }
179
180
        $cacheIdentifier = $this->getCacheIdentifier($className);
181
182
        if (false === isset($this->static[$cacheIdentifier])) {
183
            $cacheInstance = $this->getCacheInstance();
184
185
            if ($cacheInstance->has($cacheIdentifier)) {
186
                $static = $cacheInstance->get($cacheIdentifier);
187
            } else {
188
                $static = $this->buildStaticInstance($className);
189
                $static->getObjectHash();
190
191
                if (false === $static->getDefinitionValidationResult()->hasErrors()) {
192
                    $cacheInstance->set($cacheIdentifier, $static);
193
                }
194
            }
195
196
            $this->addToGlobalConfiguration($static);
197
198
            $this->static[$cacheIdentifier] = $static;
199
        }
200
201
        return $this->static[$cacheIdentifier];
202
    }
203
204
    /**
205
     * Adds the given form configuration to the global FormZ configuration
206
     * object.
207
     *
208
     * @param FormObjectStatic $static
209
     */
210
    protected function addToGlobalConfiguration(FormObjectStatic $static)
211
    {
212
        if (false === $this->getGlobalConfiguration()->hasForm($static->getClassName())) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Romm\ConfigurationObject...gurationObjectInterface as the method hasForm() does only exist in the following implementations of said interface: Romm\Formz\Configuration\Configuration.

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...
213
            $this->getGlobalConfiguration()->addForm($static);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Romm\ConfigurationObject...gurationObjectInterface as the method addForm() does only exist in the following implementations of said interface: Romm\Formz\Configuration\Configuration.

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...
214
        }
215
    }
216
217
    /**
218
     * @param string $className
219
     * @return string
220
     */
221
    protected function getCacheIdentifier($className)
222
    {
223
        $sanitizedClassName = StringService::get()->sanitizeString(str_replace('\\', '-', $className));
224
225
        return 'form-object-' . $sanitizedClassName;
226
    }
227
228
    /**
229
     * Wrapper for unit tests.
230
     *
231
     * @param string $className
232
     * @return FormObjectStatic
233
     */
234
    protected function buildStaticInstance($className)
235
    {
236
        /** @var FormObjectBuilderInterface $builder */
237
        $builder = Core::instantiate(DefaultFormObjectBuilder::class);
238
239
        return $builder->getStaticInstance($className);
240
    }
241
242
    /**
243
     * Wrapper for unit tests.
244
     *
245
     * @param FormInterface $form
246
     * @return FormObjectProxy
247
     */
248
    protected function getNewProxyInstance(FormInterface $form)
249
    {
250
        $formObject = $this->getInstanceWithFormInstance($form);
251
252
        /** @var FormObjectProxy $formObjectProxy */
253
        $formObjectProxy = Core::instantiate(FormObjectProxy::class, $formObject, $form);
254
255
        return $formObjectProxy;
256
    }
257
258
    /**
259
     * @return Configuration|ConfigurationObjectInterface
260
     */
261
    protected function getGlobalConfiguration()
262
    {
263
        return ConfigurationFactory::get()->getFormzConfiguration()->getObject(true);
264
    }
265
266
    /**
267
     * @return FrontendInterface
268
     */
269
    protected function getCacheInstance()
270
    {
271
        return CacheService::get()->getCacheInstance();
272
    }
273
}
274