Completed
Push — 3.x ( e515b3...3ee43c )
by Grégoire
04:24
created

BaseFieldDescription::getFieldGetterKey()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 10
nc 4
nop 2
1
<?php
2
3
/*
4
 * This file is part of the Sonata Project package.
5
 *
6
 * (c) Thomas Rabaix <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Sonata\AdminBundle\Admin;
13
14
use Doctrine\Common\Inflector\Inflector;
15
use Sonata\AdminBundle\Exception\NoValueException;
16
17
/**
18
 * A FieldDescription hold the information about a field. A typical
19
 * admin instance contains different collections of fields.
20
 *
21
 * - form: used by the form
22
 * - list: used by the list
23
 * - filter: used by the list filter
24
 *
25
 * Some options are global across the different contexts, other are
26
 * context specifics.
27
 *
28
 * Global options :
29
 *   - type (m): define the field type (use to tweak the form or the list)
30
 *   - template (o) : the template used to render the field
31
 *   - name (o) : the name used (label in the form, title in the list)
32
 *   - link_parameters (o) : add link parameter to the related Admin class when
33
 *                           the Admin.generateUrl is called
34
 *   - code : the method name to retrieve the related value
35
 *   - associated_tostring : (deprecated, use associated_property option)
36
 *                           the method to retrieve the "string" representation
37
 *                           of the collection element.
38
 *   - associated_property : property path to retrieve the "string" representation
39
 *                           of the collection element.
40
 *
41
 * Form Field options :
42
 *   - field_type (o): the widget class to use to render the field
43
 *   - field_options (o): the options to give to the widget
44
 *   - edit (o) : list|inline|standard (only used for associated admin)
45
 *      - list : open a popup where the user can search, filter and click on one field
46
 *               to select one item
47
 *      - inline : the associated form admin is embedded into the current form
48
 *      - standard : the associated admin is created through a popup
49
 *
50
 * List Field options :
51
 *   - identifier (o): if set to true a link appear on to edit the element
52
 *
53
 * Filter Field options :
54
 *   - options (o): options given to the Filter object
55
 *   - field_type (o): the widget class to use to render the field
56
 *   - field_options (o): the options to give to the widget
57
 *
58
 * @author Thomas Rabaix <[email protected]>
59
 */
60
abstract class BaseFieldDescription implements FieldDescriptionInterface
61
{
62
    /**
63
     * @var string the field name
64
     */
65
    protected $name;
66
67
    /**
68
     * @var string|int the type
69
     */
70
    protected $type;
71
72
    /**
73
     * @var string|int the original mapping type
74
     */
75
    protected $mappingType;
76
77
    /**
78
     * @var string the field name (of the form)
79
     */
80
    protected $fieldName;
81
82
    /**
83
     * @var array the ORM association mapping
84
     */
85
    protected $associationMapping;
86
87
    /**
88
     * @var array the ORM field information
89
     */
90
    protected $fieldMapping;
91
92
    /**
93
     * @var array the ORM parent mapping association
94
     */
95
    protected $parentAssociationMappings;
96
97
    /**
98
     * @var string the template name
99
     */
100
    protected $template;
101
102
    /**
103
     * @var array the option collection
104
     */
105
    protected $options = [];
106
107
    /**
108
     * @var AdminInterface|null the parent Admin instance
109
     */
110
    protected $parent = null;
111
112
    /**
113
     * @var AdminInterface the related admin instance
114
     */
115
    protected $admin;
116
117
    /**
118
     * @var AdminInterface the associated admin class if the object is associated to another entity
119
     */
120
    protected $associationAdmin;
121
122
    /**
123
     * @var string the help message to display
124
     */
125
    protected $help;
126
127
    /**
128
     * @var array[] cached object field getters
129
     */
130
    private static $fieldGetters = [];
131
132
    public function setFieldName($fieldName)
133
    {
134
        $this->fieldName = $fieldName;
135
    }
136
137
    public function getFieldName()
138
    {
139
        return $this->fieldName;
140
    }
141
142
    public function setName($name)
143
    {
144
        $this->name = $name;
145
146
        if (!$this->getFieldName()) {
147
            $this->setFieldName(substr(strrchr('.'.$name, '.'), 1));
148
        }
149
    }
150
151
    public function getName()
152
    {
153
        return $this->name;
154
    }
155
156
    public function getOption($name, $default = null)
157
    {
158
        return isset($this->options[$name]) ? $this->options[$name] : $default;
159
    }
160
161
    public function setOption($name, $value)
162
    {
163
        $this->options[$name] = $value;
164
    }
165
166
    public function setOptions(array $options)
167
    {
168
        // set the type if provided
169
        if (isset($options['type'])) {
170
            $this->setType($options['type']);
171
            unset($options['type']);
172
        }
173
174
        // remove property value
175
        if (isset($options['template'])) {
176
            $this->setTemplate($options['template']);
177
            unset($options['template']);
178
        }
179
180
        // set help if provided
181
        if (isset($options['help'])) {
182
            $this->setHelp($options['help']);
183
            unset($options['help']);
184
        }
185
186
        // set default placeholder
187
        if (!isset($options['placeholder'])) {
188
            $options['placeholder'] = 'short_object_description_placeholder';
189
        }
190
191
        if (!isset($options['link_parameters'])) {
192
            $options['link_parameters'] = [];
193
        }
194
195
        $this->options = $options;
196
    }
197
198
    public function getOptions()
199
    {
200
        return $this->options;
201
    }
202
203
    public function setTemplate($template)
204
    {
205
        $this->template = $template;
206
    }
207
208
    public function getTemplate()
209
    {
210
        return $this->template;
211
    }
212
213
    public function setType($type)
214
    {
215
        $this->type = $type;
216
    }
217
218
    public function getType()
219
    {
220
        return $this->type;
221
    }
222
223
    public function setParent(AdminInterface $parent)
224
    {
225
        $this->parent = $parent;
226
    }
227
228
    public function getParent()
229
    {
230
        return $this->parent;
231
    }
232
233
    public function getAssociationMapping()
234
    {
235
        return $this->associationMapping;
236
    }
237
238
    public function getFieldMapping()
239
    {
240
        return $this->fieldMapping;
241
    }
242
243
    public function getParentAssociationMappings()
244
    {
245
        return $this->parentAssociationMappings;
246
    }
247
248
    public function setAssociationAdmin(AdminInterface $associationAdmin)
249
    {
250
        $this->associationAdmin = $associationAdmin;
251
        $this->associationAdmin->setParentFieldDescription($this);
252
    }
253
254
    public function getAssociationAdmin()
255
    {
256
        return $this->associationAdmin;
257
    }
258
259
    public function hasAssociationAdmin()
260
    {
261
        return null !== $this->associationAdmin;
262
    }
263
264
    public function getFieldValue($object, $fieldName)
265
    {
266
        if ($this->isVirtual() || null === $object) {
267
            return;
268
        }
269
270
        $getters = [];
271
        $parameters = [];
272
273
        // prefer method name given in the code option
274
        if ($this->getOption('code')) {
275
            $getters[] = $this->getOption('code');
276
        }
277
        // parameters for the method given in the code option
278
        if ($this->getOption('parameters')) {
279
            $parameters = $this->getOption('parameters');
280
        }
281
282
        if (is_string($fieldName) && '' !== $fieldName) {
283
            if ($this->hasCachedFieldGetter($object, $fieldName)) {
284
                return $this->callCachedGetter($object, $fieldName, $parameters);
285
            }
286
287
            $camelizedFieldName = Inflector::classify($fieldName);
288
289
            $getters[] = 'get'.$camelizedFieldName;
290
            $getters[] = 'is'.$camelizedFieldName;
291
            $getters[] = 'has'.$camelizedFieldName;
292
        }
293
294
        foreach ($getters as $getter) {
295
            if (method_exists($object, $getter) && is_callable([$object, $getter])) {
296
                $this->cacheFieldGetter($object, $fieldName, 'getter', $getter);
297
298
                return call_user_func_array([$object, $getter], $parameters);
299
            }
300
        }
301
302
        if (method_exists($object, '__call')) {
303
            $this->cacheFieldGetter($object, $fieldName, 'call');
304
305
            return call_user_func_array([$object, '__call'], [$fieldName, $parameters]);
306
        }
307
308
        if (isset($object->{$fieldName})) {
309
            $this->cacheFieldGetter($object, $fieldName, 'var');
310
311
            return $object->{$fieldName};
312
        }
313
314
        throw new NoValueException(sprintf('Unable to retrieve the value of `%s`', $this->getName()));
315
    }
316
317
    public function setAdmin(AdminInterface $admin)
318
    {
319
        $this->admin = $admin;
320
    }
321
322
    public function getAdmin()
323
    {
324
        return $this->admin;
325
    }
326
327
    public function mergeOption($name, array $options = [])
328
    {
329
        if (!isset($this->options[$name])) {
330
            $this->options[$name] = [];
331
        }
332
333
        if (!is_array($this->options[$name])) {
334
            throw new \RuntimeException(sprintf('The key `%s` does not point to an array value', $name));
335
        }
336
337
        $this->options[$name] = array_merge($this->options[$name], $options);
338
    }
339
340
    public function mergeOptions(array $options = [])
341
    {
342
        $this->setOptions(array_merge_recursive($this->options, $options));
343
    }
344
345
    public function setMappingType($mappingType)
346
    {
347
        $this->mappingType = $mappingType;
348
    }
349
350
    public function getMappingType()
351
    {
352
        return $this->mappingType;
353
    }
354
355
    /**
356
     * Camelize a string.
357
     *
358
     * NEXT_MAJOR: remove this method.
359
     *
360
     * @static
361
     *
362
     * @param string $property
363
     *
364
     * @return string
365
     *
366
     * @deprecated Deprecated since version 3.1. Use \Doctrine\Common\Inflector\Inflector::classify() instead
367
     */
368
    public static function camelize($property)
369
    {
370
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
371
            sprintf(
372
                'The %s method is deprecated since 3.1 and will be removed in 4.0. '.
373
                'Use \Doctrine\Common\Inflector\Inflector::classify() instead.',
374
                __METHOD__
375
            ),
376
            E_USER_DEPRECATED
377
        );
378
379
        return Inflector::classify($property);
380
    }
381
382
    /**
383
     * Defines the help message.
384
     *
385
     * @param string $help
386
     */
387
    public function setHelp($help)
388
    {
389
        $this->help = $help;
390
    }
391
392
    public function getHelp()
393
    {
394
        return $this->help;
395
    }
396
397
    public function getLabel()
398
    {
399
        return $this->getOption('label');
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getOption('label'); of type array|null adds the type array to the return on line 399 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...tionInterface::getLabel of type string.
Loading history...
400
    }
401
402
    public function isSortable()
403
    {
404
        return false !== $this->getOption('sortable', false);
405
    }
406
407
    public function getSortFieldMapping()
408
    {
409
        return $this->getOption('sort_field_mapping');
410
    }
411
412
    public function getSortParentAssociationMapping()
413
    {
414
        return $this->getOption('sort_parent_association_mappings');
415
    }
416
417
    public function getTranslationDomain()
418
    {
419
        return $this->getOption('translation_domain') ?: $this->getAdmin()->getTranslationDomain();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->getOption('transl...getTranslationDomain(); of type array|string adds the type array to the return on line 419 which is incompatible with the return type declared by the interface Sonata\AdminBundle\Admin...e::getTranslationDomain of type string.
Loading history...
420
    }
421
422
    /**
423
     * Return true if field is virtual.
424
     *
425
     * @return bool
426
     */
427
    public function isVirtual()
428
    {
429
        return false !== $this->getOption('virtual_field', false);
430
    }
431
432
    private function getFieldGetterKey($object, $fieldName)
433
    {
434
        if (!is_string($fieldName)) {
435
            return null;
436
        }
437
        if (!is_object($object)) {
438
            return null;
439
        }
440
        $components = [get_class($object), $fieldName];
441
        $code = $this->getOption('code');
442
        if (is_string($code) && '' !== $code) {
443
            $components[] = $code;
444
        }
445
446
        return implode('-', $components);
447
    }
448
449
    private function hasCachedFieldGetter($object, $fieldName)
450
    {
451
        return isset(
452
            self::$fieldGetters[$this->getFieldGetterKey($object, $fieldName)]
453
        );
454
    }
455
456
    private function callCachedGetter($object, $fieldName, array $parameters = [])
457
    {
458
        $getterKey = $this->getFieldGetterKey($object, $fieldName);
459
        if (self::$fieldGetters[$getterKey]['method'] === 'getter') {
460
            return call_user_func_array(
461
                [$object, self::$fieldGetters[$getterKey]['getter']],
462
                $parameters
463
            );
464
        } elseif (self::$fieldGetters[$getterKey]['method'] === 'call') {
465
            return call_user_func_array(
466
                [$object, '__call'],
467
                [$fieldName, $parameters]
468
            );
469
        }
470
471
        return $object->{$fieldName};
472
    }
473
474
    private function cacheFieldGetter($object, $fieldName, $method, $getter = null)
475
    {
476
        $getterKey = $this->getFieldGetterKey($object, $fieldName);
477
        if (null !== $getterKey) {
478
            self::$fieldGetters[$getterKey] = [
479
                'method' => $method,
480
            ];
481
            if (null !== $getter) {
482
                self::$fieldGetters[$getterKey]['getter'] = $getter;
483
            }
484
        }
485
    }
486
}
487