Completed
Push — feature/improve-form-object-ha... ( 8bb971 )
by Romain
10:18
created

FormObjectFactory::getStaticInstance()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.439
c 0
b 0
f 0
cc 5
eloc 17
nc 5
nop 1
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\Formz\Configuration\Configuration;
17
use Romm\Formz\Configuration\ConfigurationFactory;
18
use Romm\Formz\Core\Core;
19
use Romm\Formz\Exceptions\ClassNotFoundException;
20
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
21
use Romm\Formz\Form\FormInterface;
22
use Romm\Formz\Service\CacheService;
23
use Romm\Formz\Service\StringService;
24
use Romm\Formz\Service\Traits\ExtendedSelfInstantiateTrait;
25
use Romm\Formz\Service\TypoScriptService;
26
use TYPO3\CMS\Core\SingletonInterface;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
29
30
/**
31
 * Factory class that will manage form object instances.
32
 *
33
 * You can fetch a new instance by calling one of the following methods:
34
 * `getInstanceFromFormInstance()` or `getInstanceFromClassName()`
35
 *
36
 * @see \Romm\Formz\Form\FormObject\FormObject
37
 */
38
class FormObjectFactory implements SingletonInterface
39
{
40
    use ExtendedSelfInstantiateTrait;
41
42
    const IGNORE_PROPERTY = 'formz-ignore';
43
44
    /**
45
     * @var FormObject[]
46
     */
47
    protected $instances = [];
48
49
    /**
50
     * @var FormObjectStatic[]
51
     */
52
    protected $static = [];
53
54
    /**
55
     * @var FormObjectProxy[]
56
     */
57
    protected $proxy = [];
58
59
    /**
60
     * @var Configuration
61
     */
62
    protected $globalConfiguration;
63
64
    /**
65
     * @var array
66
     */
67
    private static $ignoredProperties = ['validationData', 'uid', 'pid', '_localizedUid', '_languageUid', '_versionedUid'];
68
69
    /**
70
     * Will fetch FormZ global configuration.
71
     */
72
    public function initializeObject()
73
    {
74
        $this->globalConfiguration = ConfigurationFactory::get()->getFormzConfiguration()->getObject(true);
75
    }
76
77
    /**
78
     * @param FormInterface $form
79
     * @param string        $name
80
     * @return FormObject
81
     */
82
    public function getInstanceWithFormInstance(FormInterface $form, $name = 'defaultName')
83
    {
84
        $hash = $name . '-' . spl_object_hash($form);
85
86
        if (false === isset($this->instances[$hash])) {
87
            $this->instances[$hash] = $this->getInstanceWithClassName(get_class($form), $name);
88
            $this->instances[$hash]->setForm($form);
89
        }
90
91
        return $this->instances[$hash];
92
    }
93
94
    /**
95
     * Will create an instance of `FormObject` based on a class that implements
96
     * the interface `FormInterface`.
97
     *
98
     * @param string $className
99
     * @param string $name
100
     * @return FormObject
101
     */
102
    public function getInstanceWithClassName($className, $name)
103
    {
104
        /** @var FormObject $formObject */
105
        $formObject = Core::instantiate(FormObject::class, $name, $this->getStaticInstance($className));
106
107
        return $formObject;
108
    }
109
110
    /**
111
     * Returns the proxy object for the given form object and form instance.
112
     *
113
     * Please use with caution, as this is a very low level function!
114
     *
115
     * @param FormInterface $form
116
     * @return FormObjectProxy
117
     */
118
    public function getProxy(FormInterface $form)
119
    {
120
        $hash = spl_object_hash($form);
121
122
        if (false === isset($this->proxy[$hash])) {
123
            $this->proxy[$hash] = $this->getNewProxyInstance($form);
124
        }
125
126
        return $this->proxy[$hash];
127
    }
128
129
    /**
130
     * @param string $className
131
     * @return FormObjectStatic
132
     * @throws ClassNotFoundException
133
     * @throws InvalidArgumentTypeException
134
     */
135
    protected function getStaticInstance($className)
136
    {
137
        if (false === class_exists($className)) {
138
            throw ClassNotFoundException::wrongFormClassName($className);
139
        }
140
141
        if (false === in_array(FormInterface::class, class_implements($className))) {
142
            throw InvalidArgumentTypeException::wrongFormType($className);
143
        }
144
145
        $cacheIdentifier = $this->getCacheIdentifier($className);
146
147
        if (false === isset($this->static[$cacheIdentifier])) {
148
            $cacheInstance = CacheService::get()->getCacheInstance();
149
150
            if ($cacheInstance->has($cacheIdentifier)) {
151
                $static = $cacheInstance->get($cacheIdentifier);
152
            } else {
153
                $static = $this->buildStaticInstance($className);
154
                $static->getObjectHash();
155
156
                $cacheInstance->set($cacheIdentifier, $static);
157
            }
158
159
            $this->addToGlobalConfiguration($static);
160
161
            $this->static[$cacheIdentifier] = $static;
162
        }
163
164
        return $this->static[$cacheIdentifier];
165
    }
166
167
    /**
168
     * Adds the given form configuration to the global FormZ configuration
169
     * object.
170
     *
171
     * @param FormObjectStatic $static
172
     */
173
    public function addToGlobalConfiguration(FormObjectStatic $static)
174
    {
175
        if (false === $this->globalConfiguration->hasForm($static->getClassName())) {
176
            $this->globalConfiguration->addForm($static);
177
        }
178
    }
179
180
    /**
181
     * @param string $className
182
     * @return string
183
     */
184
    protected function getCacheIdentifier($className)
185
    {
186
        $sanitizedClassName = StringService::get()->sanitizeString(str_replace('\\', '-', $className));
187
188
        return 'form-object-' . $sanitizedClassName;
189
    }
190
191
    /**
192
     * Wrapper for unit tests.
193
     *
194
     * @param string $className
195
     * @return FormObjectStatic
196
     */
197
    protected function buildStaticInstance($className)
198
    {
199
        /** @var FormObjectStatic $static */
200
        $static = Core::instantiate(
201
            FormObjectStatic::class,
202
            $className,
203
            TypoScriptService::get()->getFormConfiguration($className)
204
        );
205
206
        $this->insertObjectProperties($static);
207
208
        return $static;
209
    }
210
211
    /**
212
     * Will insert all the accessible properties of the given instance.
213
     *
214
     * @param FormObjectStatic $instance
215
     */
216
    protected function insertObjectProperties(FormObjectStatic $instance)
217
    {
218
        $className = $instance->getClassName();
219
220
        /** @var ReflectionService $reflectionService */
221
        $reflectionService = GeneralUtility::makeInstance(ReflectionService::class);
222
        $reflectionProperties = $reflectionService->getClassPropertyNames($className);
223
224
        $classReflection = new \ReflectionClass($className);
225
        $publicProperties = $classReflection->getProperties(\ReflectionProperty::IS_PUBLIC);
226
227
        foreach ($reflectionProperties as $property) {
228
            if (false === in_array($property, self::$ignoredProperties)
229
                && false === $reflectionService->isPropertyTaggedWith($className, $property, self::IGNORE_PROPERTY)
230
                && ((true === in_array($property, $publicProperties))
231
                    || $reflectionService->hasMethod($className, 'get' . ucfirst($property))
232
                )
233
            ) {
234
                $instance->addProperty($property);
235
            }
236
        }
237
238
        unset($publicProperties);
239
    }
240
241
    /**
242
     * Wrapper for unit tests.
243
     *
244
     * @param FormInterface $form
245
     * @return FormObjectProxy
246
     */
247
    protected function getNewProxyInstance(FormInterface $form)
248
    {
249
        $formObject = $this->getInstanceWithFormInstance($form);
250
251
        /** @var FormObjectProxy $formObjectProxy */
252
        $formObjectProxy = Core::instantiate(FormObjectProxy::class, $formObject, $form);
253
254
        return $formObjectProxy;
255
    }
256
}
257