Proxy::setOptions()   F
last analyzed

Complexity

Conditions 12
Paths 2048

Size

Total Lines 48

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 48
ccs 25
cts 25
cp 1
rs 2.8
c 0
b 0
f 0
cc 12
nc 2048
nop 1
crap 12

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace DoctrineModule\Form\Element;
6
7
use Doctrine\Common\Collections\Collection;
8
use Doctrine\Common\Inflector\Inflector;
9
use Doctrine\Persistence\ObjectManager;
10
use DoctrineModule\Persistence\ObjectManagerAwareInterface;
11
use Laminas\Stdlib\Guard\ArrayOrTraversableGuardTrait;
12
use ReflectionMethod;
13
use RuntimeException;
14
use Traversable;
15
use function array_change_key_case;
16
use function array_key_exists;
17
use function array_shift;
18
use function call_user_func;
19
use function count;
20
use function current;
21
use function get_class;
22
use function gettype;
23
use function interface_exists;
24
use function is_callable;
25
use function is_object;
26
use function is_string;
27
use function method_exists;
28
use function sprintf;
29
use function strtolower;
30
use function trim;
31
32
class Proxy implements ObjectManagerAwareInterface
33
{
34
    use ArrayOrTraversableGuardTrait;
35
36
    /** @var mixed[]|Traversable */
37
    protected $objects;
38
39
    /** @var string */
40
    protected $targetClass;
41
42
    /** @var mixed[] */
43
    protected $valueOptions = [];
44
45
    /** @var mixed[] */
46
    protected $findMethod = [];
47
48
    /** @var mixed */
49
    protected $property;
50
51
    /** @var mixed[] */
52
    protected $optionAttributes = [];
53
54
    /** @var callable $labelGenerator A callable used to create a label based on an item in the collection an Entity */
55
    protected $labelGenerator;
56
57
    /** @var bool|null */
58
    protected $isMethod;
59
60
    /** @var ObjectManager */
61
    protected $objectManager;
62
63
    /** @var bool */
64
    protected $displayEmptyItem = false;
65
66
    /** @var string */
67
    protected $emptyItemLabel = '';
68
69
    /** @var string|null */
70
    protected $optgroupIdentifier;
71
72
    /** @var string|null */
73
    protected $optgroupDefault;
74
75
    /**
76
     * @param mixed[] $options
77
     */
78 23
    public function setOptions(array $options) : void
79
    {
80 23
        if (isset($options['object_manager'])) {
81 22
            $this->setObjectManager($options['object_manager']);
82
        }
83
84 23
        if (isset($options['target_class'])) {
85 22
            $this->setTargetClass($options['target_class']);
86
        }
87
88 23
        if (isset($options['property'])) {
89 3
            $this->setProperty($options['property']);
90
        }
91
92 23
        if (isset($options['label_generator'])) {
93 2
            $this->setLabelGenerator($options['label_generator']);
0 ignored issues
show
Documentation introduced by
$options['label_generator'] is of type *, but the function expects a callable.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
94
        }
95
96 23
        if (isset($options['find_method'])) {
97 4
            $this->setFindMethod($options['find_method']);
98
        }
99
100 23
        if (isset($options['is_method'])) {
101 1
            $this->setIsMethod($options['is_method']);
102
        }
103
104 23
        if (isset($options['display_empty_item'])) {
105 1
            $this->setDisplayEmptyItem($options['display_empty_item']);
106
        }
107
108 23
        if (isset($options['empty_item_label'])) {
109 1
            $this->setEmptyItemLabel($options['empty_item_label']);
110
        }
111
112 23
        if (isset($options['option_attributes'])) {
113 3
            $this->setOptionAttributes($options['option_attributes']);
114
        }
115
116 23
        if (isset($options['optgroup_identifier'])) {
117 4
            $this->setOptgroupIdentifier($options['optgroup_identifier']);
118
        }
119
120 23
        if (! isset($options['optgroup_default'])) {
121 23
            return;
122
        }
123
124 1
        $this->setOptgroupDefault($options['optgroup_default']);
125 1
    }
126
127
    /**
128
     * @return mixed
129
     */
130 22
    public function getValueOptions()
131
    {
132 22
        if (empty($this->valueOptions)) {
133 22
            $this->loadValueOptions();
134
        }
135
136 14
        return $this->valueOptions;
137
    }
138
139
    /**
140
     * @return mixed
141
     */
142 20
    public function getObjects()
143
    {
144 20
        $this->loadObjects();
145
146 16
        return $this->objects;
147
    }
148
149
    /**
150
     * Set the label for the empty option
151
     */
152 1
    public function setEmptyItemLabel(string $emptyItemLabel) : Proxy
153
    {
154 1
        $this->emptyItemLabel = $emptyItemLabel;
155
156 1
        return $this;
157
    }
158
159 1
    public function getEmptyItemLabel() : string
160
    {
161 1
        return $this->emptyItemLabel;
162
    }
163
164
    /**
165
     * @return mixed[]
166
     */
167 14
    public function getOptionAttributes() : array
168
    {
169 14
        return $this->optionAttributes;
170
    }
171
172
    /**
173
     * @param mixed[] $optionAttributes
174
     */
175 3
    public function setOptionAttributes(array $optionAttributes) : void
176
    {
177 3
        $this->optionAttributes = $optionAttributes;
178 3
    }
179
180
    /**
181
     * Set a flag, whether to include the empty option at the beginning or not
182
     */
183 1
    public function setDisplayEmptyItem(bool $displayEmptyItem) : Proxy
184
    {
185 1
        $this->displayEmptyItem = $displayEmptyItem;
186
187 1
        return $this;
188
    }
189
190
    public function getDisplayEmptyItem() : bool
191
    {
192
        return $this->displayEmptyItem;
193
    }
194
195
    /**
196
     * Set the object manager
197
     */
198 22
    public function setObjectManager(ObjectManager $objectManager) : void
199
    {
200 22
        $this->objectManager = $objectManager;
201 22
    }
202
203
    /**
204
     * Get the object manager
205
     */
206 20
    public function getObjectManager() : ObjectManager
207
    {
208 20
        return $this->objectManager;
209
    }
210
211
    /**
212
     * Set the FQCN of the target object
213
     */
214 22
    public function setTargetClass(string $targetClass) : Proxy
215
    {
216 22
        $this->targetClass = $targetClass;
217
218 22
        return $this;
219
    }
220
221
    /**
222
     * Get the target class
223
     */
224 20
    public function getTargetClass() : string
225
    {
226 20
        return $this->targetClass;
227
    }
228
229
    /**
230
     * Set the property to use as the label in the options
231
     */
232 3
    public function setProperty(string $property) : Proxy
233
    {
234 3
        $this->property = $property;
235
236 3
        return $this;
237
    }
238
239
    /**
240
     * @return mixed
241
     */
242
    public function getProperty()
243
    {
244
        return $this->property;
245
    }
246
247
    /**
248
     * Set the label generator callable that is responsible for generating labels for the items in the collection
249
     *
250
     * @param callable $callable A callable used to create a label based off of an Entity
251
     */
252 1
    public function setLabelGenerator(callable $callable) : void
253
    {
254 1
        $this->labelGenerator = $callable;
255 1
    }
256
257 14
    public function getLabelGenerator() : ?callable
258
    {
259 14
        return $this->labelGenerator;
260
    }
261
262 13
    public function getOptgroupIdentifier() : ?string
263
    {
264 13
        return $this->optgroupIdentifier;
265
    }
266
267 4
    public function setOptgroupIdentifier(string $optgroupIdentifier) : void
268
    {
269 4
        $this->optgroupIdentifier = (string) $optgroupIdentifier;
270 4
    }
271
272 2
    public function getOptgroupDefault() : ?string
273
    {
274 2
        return $this->optgroupDefault;
275
    }
276
277 1
    public function setOptgroupDefault(string $optgroupDefault) : void
278
    {
279 1
        $this->optgroupDefault = (string) $optgroupDefault;
280 1
    }
281
282
    /**
283
     * Set if the property is a method to use as the label in the options
284
     */
285 1
    public function setIsMethod(bool $method) : Proxy
286
    {
287 1
        $this->isMethod = (bool) $method;
288
289 1
        return $this;
290
    }
291
292 3
    public function getIsMethod() : ?bool
293
    {
294 3
        return $this->isMethod;
295
    }
296
297
    /** Set the findMethod property to specify the method to use on repository
298
     *
299
     * @param mixed[] $findMethod
300
     */
301 4
    public function setFindMethod(array $findMethod) : Proxy
302
    {
303 4
        $this->findMethod = $findMethod;
304
305 4
        return $this;
306
    }
307
308
    /**
309
     * Get findMethod definition
310
     *
311
     * @return mixed[]
312
     */
313 20
    public function getFindMethod() : array
314
    {
315 20
        return $this->findMethod;
316
    }
317
318
    /**
319
     * @param mixed $targetEntity
320
     */
321 14
    protected function generateLabel($targetEntity) : ?string
322
    {
323 14
        if ($this->getLabelGenerator() === null) {
324 13
            return null;
325
        }
326
327 1
        return call_user_func($this->getLabelGenerator(), $targetEntity);
328
    }
329
330
    /**
331
     * @param mixed $value
332
     *
333
     * @return mixed[]|mixed|object
334
     *
335
     * @throws RuntimeException
336
     */
337
    public function getValue($value)
338
    {
339
        if (! $this->getObjectManager()) {
340
            throw new RuntimeException('No object manager was set');
341
        }
342
343
        if (! $this->getTargetClass()) {
344
            throw new RuntimeException('No target class was set');
345
        }
346
347
        $metadata = $this->getObjectManager()->getClassMetadata($this->getTargetClass());
348
349
        if (is_object($value)) {
350
            if ($value instanceof Collection) {
351
                $data = [];
352
353
                foreach ($value as $object) {
354
                    $values = $metadata->getIdentifierValues($object);
355
                    $data[] = array_shift($values);
356
                }
357
358
                $value = $data;
359
            } else {
360
                $metadata   = $this->getObjectManager()->getClassMetadata(get_class($value));
361
                $identifier = $metadata->getIdentifierFieldNames();
362
363
                // TODO: handle composite (multiple) identifiers
364
                if ($identifier !== null && count($identifier) > 1) {
365
                    //$value = $key;
366
                    $todo = true;
0 ignored issues
show
Unused Code introduced by
$todo is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
367
                } else {
368
                    $value = current($metadata->getIdentifierValues($value));
369
                }
370
            }
371
        }
372
373
        return $value;
374
    }
375
376
    /**
377
     * Load objects
378
     *
379
     * @throws RuntimeException
380
     * @throws Exception\InvalidRepositoryResultException
381
     */
382 20
    protected function loadObjects() : void
383
    {
384 20
        if (! empty($this->objects)) {
385
            return;
386
        }
387
388 20
        $findMethod = (array) $this->getFindMethod();
389
390 20
        if (! $findMethod) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $findMethod of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
391 16
            $findMethodName = 'findAll';
392 16
            $repository     = $this->objectManager->getRepository($this->targetClass);
393 16
            $objects        = $repository->findAll();
394
        } else {
395 4
            if (! isset($findMethod['name'])) {
396 1
                throw new RuntimeException('No method name was set');
397
            }
398
399 3
            $findMethodName   = $findMethod['name'];
400 3
            $findMethodParams = isset($findMethod['params']) ? array_change_key_case($findMethod['params']) : [];
401 3
            $repository       = $this->objectManager->getRepository($this->targetClass);
402
403 3
            if (! method_exists($repository, $findMethodName)) {
404 1
                throw new RuntimeException(
405 1
                    sprintf(
406 1
                        'Method "%s" could not be found in repository "%s"',
407 1
                        $findMethodName,
408 1
                        get_class($repository)
409
                    )
410
                );
411
            }
412
413 2
            $r    = new ReflectionMethod($repository, $findMethodName);
414 2
            $args = [];
415
416 2
            foreach ($r->getParameters() as $param) {
417 2
                if (array_key_exists(strtolower($param->getName()), $findMethodParams)) {
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
418 1
                    $args[] = $findMethodParams[strtolower($param->getName())];
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
419 2
                } elseif ($param->isDefaultValueAvailable()) {
420 1
                    $args[] = $param->getDefaultValue();
421 1
                } elseif (! $param->isOptional()) {
422 1
                    throw new RuntimeException(
423 1
                        sprintf(
424
                            'Required parameter "%s" with no default value for method "%s" in repository "%s"'
425 1
                            . ' was not provided',
426 1
                            $param->getName(),
0 ignored issues
show
Bug introduced by
Consider using $param->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
427 1
                            $findMethodName,
428 1
                            get_class($repository)
429
                        )
430
                    );
431
                }
432
            }
433
434 1
            $objects = $r->invokeArgs($repository, $args);
435
        }
436
437 17
        $this->guardForArrayOrTraversable(
438 17
            $objects,
439 17
            sprintf('%s::%s() return value', get_class($repository), $findMethodName),
440 17
            'DoctrineModule\Form\Element\Exception\InvalidRepositoryResultException'
441
        );
442
443 16
        $this->objects = $objects;
444 16
    }
445
446
    /**
447
     * Load value options
448
     *
449
     * @throws RuntimeException
450
     */
451 22
    protected function loadValueOptions() : void
452
    {
453 22
        if (! $this->objectManager) {
454 1
            throw new RuntimeException('No object manager was set');
455
        }
456
457 21
        if (! $this->targetClass) {
458 1
            throw new RuntimeException('No target class was set');
459
        }
460
461 20
        $metadata         = $this->getObjectManager()->getClassMetadata($this->getTargetClass());
462 20
        $identifier       = $metadata->getIdentifierFieldNames();
463 20
        $objects          = $this->getObjects();
464 16
        $options          = [];
465 16
        $optionAttributes = [];
466
467 16
        if ($this->displayEmptyItem) {
468 1
            $options[''] = $this->getEmptyItemLabel();
469
        }
470
471 16
        foreach ($objects as $key => $object) {
472 14
            $generatedLabel = $this->generateLabel($object);
473 14
            if ($generatedLabel !== null) {
474 1
                $label = $generatedLabel;
475 13
            } elseif ($this->property) {
476 3
                $property = $this->property;
477 3
                if (($this->getIsMethod() === false || $this->getIsMethod() === null)
478 3
                    && ! $metadata->hasField($property)
479
                ) {
480
                    throw new RuntimeException(
481
                        sprintf(
482
                            'Property "%s" could not be found in object "%s"',
483
                            $property,
484
                            $this->getTargetClass()
485
                        )
486
                    );
487
                }
488
489 3
                $getter = 'get' . Inflector::classify($property);
490
491 3
                if (! is_callable([$object, $getter])) {
492
                    throw new RuntimeException(
493
                        sprintf('Method "%s::%s" is not callable', $this->targetClass, $getter)
494
                    );
495
                }
496
497 3
                $label = $object->{$getter}();
498
            } else {
499 10
                if (! is_callable([$object, '__toString'])) {
500
                    throw new RuntimeException(
501
                        sprintf(
502
                            '%s must have a "__toString()" method defined if you have not set a property'
503
                            . ' or method to use.',
504
                            $this->getTargetClass()
505
                        )
506
                    );
507
                }
508
509 10
                $label = (string) $object;
510
            }
511
512 14
            if ($identifier !== null && count($identifier) > 1) {
513
                $value = $key;
514
            } else {
515 14
                $value = current($metadata->getIdentifierValues($object));
516
            }
517
518 14
            foreach ($this->getOptionAttributes() as $optionKey => $optionValue) {
519 3
                if (is_string($optionValue)) {
520 1
                    $optionAttributes[$optionKey] = $optionValue;
521
522 1
                    continue;
523
                }
524
525 2
                if (is_callable($optionValue)) {
526 1
                    $callableValue                = call_user_func($optionValue, $object);
527 1
                    $optionAttributes[$optionKey] = (string) $callableValue;
528
529 1
                    continue;
530
                }
531
532 1
                throw new RuntimeException(
533 1
                    sprintf(
534
                        'Parameter "option_attributes" expects an array of key => value where value is of type'
535 1
                        . '"string" or "callable". Value of type "%s" found.',
536 1
                        gettype($optionValue)
537
                    )
538
                );
539
            }
540
541
            // If no optgroup_identifier has been configured, apply default handling and continue
542 13
            if ($this->getOptgroupIdentifier() === null) {
543 9
                $options[] = ['label' => $label, 'value' => $value, 'attributes' => $optionAttributes];
544
545 9
                continue;
546
            }
547
548
            // optgroup_identifier found, handle grouping
549 4
            $optgroupGetter = 'get' . Inflector::classify($this->getOptgroupIdentifier());
550
551 4
            if (! is_callable([$object, $optgroupGetter])) {
552 1
                throw new RuntimeException(
553 1
                    sprintf('Method "%s::%s" is not callable', $this->targetClass, $optgroupGetter)
554
                );
555
            }
556
557 3
            $optgroup = $object->{$optgroupGetter}();
558
559
            // optgroup_identifier contains a valid group-name. Handle default grouping.
560 3
            if ($optgroup !== null && trim($optgroup) !== '') {
561 2
                $options[$optgroup]['label']     = $optgroup;
562 2
                $options[$optgroup]['options'][] = [
563 2
                    'label'      => $label,
564 2
                    'value'      => $value,
565 2
                    'attributes' => $optionAttributes,
566
                ];
567
568 2
                continue;
569
            }
570
571 2
            $optgroupDefault = $this->getOptgroupDefault();
572
573
            // No optgroup_default has been provided. Line up without a group
574 2
            if ($optgroupDefault === null) {
575 1
                $options[] = ['label' => $label, 'value' => $value, 'attributes' => $optionAttributes];
576
577 1
                continue;
578
            }
579
580
            // Line up entry with optgroup_default
581 1
            $options[$optgroupDefault]['label']     = $optgroupDefault;
582 1
            $options[$optgroupDefault]['options'][] = [
583 1
                'label'      => $label,
584 1
                'value'      => $value,
585 1
                'attributes' => $optionAttributes,
586
            ];
587
        }
588
589 14
        $this->valueOptions = $options;
590 14
    }
591
}
592
593
interface_exists(ObjectManager::class);
594