IntrospectionHelper   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 618
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 84
eloc 227
dl 0
loc 618
rs 2
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
D __construct() 0 125 27
C createElement() 0 94 16
A getPropertyName() 0 5 1
A addText() 0 13 3
A storeElement() 0 19 4
C setAttribute() 0 92 15
A getNestedElements() 0 3 1
A supportsCharacters() 0 3 1
A getElementName() 0 32 6
A getAttributes() 0 8 2
A getHelper() 0 7 2
A warn() 0 4 2
A getClassnameFromParameter() 0 8 3
A isContainer() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like IntrospectionHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IntrospectionHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing;
22
23
use Exception;
24
use Phing\Exception\BuildException;
25
use Phing\Io\File;
26
use Phing\Parser\CustomChildCreator;
27
use Phing\Parser\DynamicAttribute;
28
use Phing\Type\Path;
29
use Phing\Type\Reference;
30
use Phing\Util\Register;
31
use Phing\Util\StringHelper;
32
use ReflectionClass;
33
use ReflectionType;
34
35
/**
36
 * Helper class that collects the methods that a task or nested element
37
 * holds to set attributes, create nested elements or hold PCDATA
38
 * elements.
39
 *
40
 *<ul>
41
 * <li><strong>SMART-UP INLINE DOCS</strong></li>
42
 * <li><strong>POLISH-UP THIS CLASS</strong></li>
43
 *</ul>
44
 *
45
 * @author    Andreas Aderhold <[email protected]>
46
 * @author    Hans Lellelid <[email protected]>
47
 * @copyright 2001,2002 THYRELL. All rights reserved
48
 */
49
class IntrospectionHelper
50
{
51
    /**
52
     * Holds the attribute setter methods.
53
     *
54
     * @var array string[]
55
     */
56
    private $attributeSetters = [];
57
58
    /**
59
     * Holds methods to create nested elements.
60
     *
61
     * @var array string[]
62
     */
63
    private $nestedCreators = [];
64
65
    /**
66
     * Holds methods to store configured nested elements.
67
     *
68
     * @var array string[]
69
     */
70
    private $nestedStorers = [];
71
72
    /**
73
     * Map from attribute names to nested types.
74
     */
75
    private $nestedTypes = [];
76
77
    /**
78
     * New idea in phing: any class can register certain
79
     * keys -- e.g. "task.current_file" -- which can be used in
80
     * task attributes, if supported.  In the build XML these
81
     * are referred to like this:
82
     *         <regexp pattern="\n" replace="%{task.current_file}"/>
83
     * In the type/task a listener method must be defined:
84
     *         function setListeningReplace($slot) {}.
85
     *
86
     * @var array string[]
87
     */
88
    private $slotListeners = [];
89
90
    /**
91
     * The method to add PCDATA stuff.
92
     *
93
     * @var string Method name of the addText (redundant?) method, if class supports it :)
94
     */
95
    private $methodAddText;
96
97
    /**
98
     * The Class that's been introspected.
99
     *
100
     * @var object
101
     */
102
    private $bean;
103
104
    /**
105
     * The cache of IntrospectionHelper classes instantiated by getHelper().
106
     *
107
     * @var array IntrospectionHelpers[]
108
     */
109
    private static $helpers = [];
110
111
    /**
112
     * This function constructs a new introspection helper for a specific class.
113
     *
114
     * This method loads all methods for the specified class and categorizes them
115
     * as setters, creators, slot listeners, etc.  This way, the setAttribue() doesn't
116
     * need to perform any introspection -- either the requested attribute setter/creator
117
     * exists or it does not & a BuildException is thrown.
118
     *
119
     * @param string $class the classname for this IH
120
     *
121
     * @throws BuildException
122
     */
123
    public function __construct($class)
124
    {
125
        $this->bean = new ReflectionClass($class);
126
127
        //$methods = get_class_methods($bean);
128
        foreach ($this->bean->getMethods() as $method) {
129
            if ($method->isPublic()) {
130
                // We're going to keep case-insensitive method names
131
                // for as long as we're allowed :)  It makes it much
132
                // easier to map XML attributes to PHP class method names.
133
                $name = strtolower($method->getName());
134
135
                // There are a few "reserved" names that might look like attribute setters
136
                // but should actually just be skipped.  (Note: this means you can't ever
137
                // have an attribute named "location" or "tasktype" or a nested element container
138
                // named "task" [TaskContainer::addTask(Task)].)
139
                if (
140
                    'setlocation' === $name
141
                    || 'settasktype' === $name
142
                    || ('addtask' === $name
143
                        && $this->isContainer()
144
                        && 1 === count($method->getParameters())
145
                        && Task::class === $method->getParameters()[0])
146
                ) {
147
                    continue;
148
                }
149
150
                if ('addtext' === $name) {
151
                    $this->methodAddText = $method;
152
                } elseif (0 === strpos($name, 'setlistening')) {
153
                    // Phing supports something unique called "RegisterSlots"
154
                    // These are dynamic values that use a basic slot system so that
155
                    // classes can register to listen to specific slots, and the value
156
                    // will always be grabbed from the slot (and never set in the project
157
                    // component).  This is useful for things like tracking the current
158
                    // file being processed by a filter (e.g. AppendTask sets an append.current_file
159
                    // slot, which can be ready by the XSLTParam type.)
160
161
                    if (1 !== count($method->getParameters())) {
162
                        throw new BuildException(
163
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take exactly one parameter.'
164
                        );
165
                    }
166
167
                    $this->slotListeners[$name] = $method;
168
                } elseif (0 === strpos($name, 'set') && 1 === count($method->getParameters())) {
169
                    $this->attributeSetters[$name] = $method;
170
                } elseif (0 === strpos($name, 'create')) {
171
                    if ($method->getNumberOfRequiredParameters() > 0) {
172
                        throw new BuildException(
173
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() may not take any parameters.'
174
                        );
175
                    }
176
177
                    if ($method->hasReturnType()) {
178
                        $this->nestedTypes[$name] = $method->getReturnType();
179
                    } else {
180
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
181
                        if (!empty($matches[1]) && class_exists($matches[1], false)) {
182
                            $this->nestedTypes[$name] = $matches[1];
183
                        } else {
184
                            // assume that method createEquals() creates object of type "Equals"
185
                            // (that example would be false, of course)
186
                            $this->nestedTypes[$name] = $this->getPropertyName($name, 'create');
187
                        }
188
                    }
189
190
                    $this->nestedCreators[$name] = $method;
191
                } elseif (0 === strpos($name, 'addconfigured')) {
192
                    // *must* use class hints if using addConfigured ...
193
194
                    // 1 param only
195
                    $params = $method->getParameters();
196
197
                    if (count($params) < 1) {
198
                        throw new BuildException(
199
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
200
                        );
201
                    }
202
203
                    if (count($params) > 1) {
204
                        $this->warn(
205
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() takes more than one parameter. (IH only uses the first)'
206
                        );
207
                    }
208
209
                    $classname = $this->getClassnameFromParameter($params[0]);
210
211
                    if (null === $classname) {
212
                        throw new BuildException(
213
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
214
                        );
215
                    }
216
217
                    $this->nestedTypes[$name] = $classname;
218
219
                    $this->nestedStorers[$name] = $method;
220
                } elseif (0 === strpos($name, 'add')) {
221
                    // *must* use class hints if using add ...
222
223
                    // 1 param only
224
                    $params = $method->getParameters();
225
                    if (count($params) < 1) {
226
                        throw new BuildException(
227
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() must take at least one parameter.'
228
                        );
229
                    }
230
231
                    if (count($params) > 1) {
232
                        $this->warn(
233
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() takes more than one parameter. (IH only uses the first)'
234
                        );
235
                    }
236
237
                    $classname = $this->getClassnameFromParameter($params[0]);
238
239
                    // we don't use the classname here, but we need to make sure it exists before
240
                    // we later try to instantiate a non-existent class
241
                    if (null === $classname) {
242
                        throw new BuildException(
243
                            $method->getDeclaringClass()->getName() . '::' . $method->getName() . '() method MUST use a class hint to indicate the class type of parameter.'
244
                        );
245
                    }
246
247
                    $this->nestedCreators[$name] = $method;
248
                }
249
            } // if $method->isPublic()
250
        } // foreach
251
    }
252
253
    /**
254
     * Factory method for helper objects.
255
     *
256
     * @param string $class The class to create a Helper for
257
     *
258
     * @return IntrospectionHelper
259
     */
260
    public static function getHelper($class)
261
    {
262
        if (!isset(self::$helpers[$class])) {
263
            self::$helpers[$class] = new IntrospectionHelper($class);
264
        }
265
266
        return self::$helpers[$class];
267
    }
268
269
    /**
270
     * Indicates whether the introspected class is a task container, supporting arbitrary nested tasks/types.
271
     *
272
     * @return bool true if the introspected class is a container; false otherwise
273
     */
274
    public function isContainer()
275
    {
276
        return $this->bean->implementsInterface(TaskContainer::class);
277
    }
278
279
    /**
280
     * Sets the named attribute.
281
     *
282
     * @param object $element
283
     * @param string $attributeName
284
     * @param mixed  $value
285
     *
286
     * @throws BuildException
287
     */
288
    public function setAttribute(Project $project, $element, $attributeName, &$value)
289
    {
290
        // we want to check whether the value we are setting looks like
291
        // a slot-listener variable:  %{task.current_file}
292
        //
293
        // slot-listener variables are not like properties, in that they cannot be mixed with
294
        // other text values.  The reason for this disparity is that properties are only
295
        // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
296
        //
297
        // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
298
        // typing.
299
        if (StringHelper::isSlotVar($value)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression Phing\Util\StringHelper::isSlotVar($value) of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
300
            $as = 'setlistening' . strtolower($attributeName);
301
302
            if (!isset($this->slotListeners[$as])) {
303
                $msg = $this->getElementName(
304
                    $project,
305
                    $element
306
                ) . " doesn't support a slot-listening '{$attributeName}' attribute.";
307
308
                throw new BuildException($msg);
309
            }
310
311
            $method = $this->slotListeners[$as];
312
313
            $key = StringHelper::slotVar($value);
314
            $value = Register::getSlot(
315
                $key
316
            ); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
317
        } else {
318
            // Traditional value options
319
320
            $as = 'set' . strtolower($attributeName);
321
322
            if (!isset($this->attributeSetters[$as])) {
323
                if ($element instanceof DynamicAttribute) {
324
                    $element->setDynamicAttribute($attributeName, (string) $value);
325
326
                    return;
327
                }
328
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$attributeName}' attribute.";
329
330
                throw new BuildException($msg);
331
            }
332
333
            $method = $this->attributeSetters[$as];
334
335
            if ('setrefid' == $as) {
336
                $value = new Reference($project, $value);
337
            } else {
338
                $params = $method->getParameters();
339
340
                /** @var ReflectionType $hint */
341
                $reflectedAttr = ($hint = $params[0]->getType()) ? $hint->getName() : null;
0 ignored issues
show
Bug introduced by
The method getName() does not exist on ReflectionType. It seems like you code against a sub-type of ReflectionType such as ReflectionNamedType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

341
                $reflectedAttr = ($hint = $params[0]->getType()) ? $hint->/** @scrutinizer ignore-call */ getName() : null;
Loading history...
342
343
                // value is a string representation of a bool type,
344
                // convert it to primitive
345
                if ('bool' === $reflectedAttr || ('string' !== $reflectedAttr && StringHelper::isBoolean($value))) {
346
                    $value = StringHelper::booleanValue($value);
347
                }
348
349
                // there should only be one param; we'll just assume ....
350
                if (null !== $reflectedAttr) {
351
                    switch ($reflectedAttr) {
352
                        case File::class:
353
                            $value = $project->resolveFile($value);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type boolean; however, parameter $fileName of Phing\Project::resolveFile() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

353
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
354
355
                            break;
356
357
                        case Path::class:
358
                            $value = new Path($project, $value);
359
360
                            break;
361
362
                        case Reference::class:
363
                            $value = new Reference($project, $value);
364
365
                            break;
366
                        // any other object params we want to support should go here ...
367
                    }
368
                } // if hint !== null
369
            } // if not setrefid
370
        } // if is slot-listener
371
372
        try {
373
            $project->log(
374
                '    -calling setter ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
375
                Project::MSG_DEBUG
376
            );
377
            $method->invoke($element, $value);
378
        } catch (Exception $exc) {
379
            throw new BuildException($exc->getMessage(), $exc);
380
        }
381
    }
382
383
    /**
384
     * Adds PCDATA areas.
385
     *
386
     * @param string $element
387
     * @param string $text
388
     *
389
     * @throws BuildException
390
     */
391
    public function addText(Project $project, $element, $text)
392
    {
393
        if (null === $this->methodAddText) {
394
            $msg = $this->getElementName($project, $element) . " doesn't support nested text data.";
0 ignored issues
show
Bug introduced by
$element of type string is incompatible with the type object expected by parameter $element of Phing\IntrospectionHelper::getElementName(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

394
            $msg = $this->getElementName($project, /** @scrutinizer ignore-type */ $element) . " doesn't support nested text data.";
Loading history...
395
396
            throw new BuildException($msg);
397
        }
398
399
        try {
400
            $method = $this->methodAddText;
401
            $method->invoke($element, $text);
402
        } catch (Exception $exc) {
403
            throw new BuildException($exc->getMessage(), $exc);
404
        }
405
    }
406
407
    /**
408
     * Creates a named nested element.
409
     *
410
     * Valid creators can be in the form createFoo() or addFoo(Bar).
411
     *
412
     * @param object $element     Object the XML tag is child of.
413
     *                            Often a task object.
414
     * @param string $elementName XML tag name
415
     *
416
     * @throws BuildException
417
     *
418
     * @return object returns the nested element
419
     */
420
    public function createElement(Project $project, $element, $elementName)
421
    {
422
        $addMethod = 'add' . strtolower($elementName);
423
        $createMethod = 'create' . strtolower($elementName);
424
        $nestedElement = null;
425
426
        if (isset($this->nestedCreators[$createMethod])) {
427
            $method = $this->nestedCreators[$createMethod];
428
429
            try { // try to invoke the creator method on object
430
                $project->log(
431
                    '    -calling creator ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
432
                    Project::MSG_DEBUG
433
                );
434
                $nestedElement = $method->invoke($element);
435
            } catch (Exception $exc) {
436
                throw new BuildException($exc->getMessage(), $exc);
437
            }
438
        } elseif (isset($this->nestedCreators[$addMethod])) {
439
            $method = $this->nestedCreators[$addMethod];
440
441
            // project components must use class hints to support the add methods
442
443
            try { // try to invoke the adder method on object
444
                $project->log(
445
                    '    -calling adder ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
446
                    Project::MSG_DEBUG
447
                );
448
                // we've already assured that correct num of params
449
                // exist and that method is using class hints
450
                $params = $method->getParameters();
451
452
                $classname = $this->getClassnameFromParameter($params[0]);
453
454
                // create a new instance of the object and add it via $addMethod
455
                $clazz = new ReflectionClass($classname);
456
                if (null !== $clazz->getConstructor() && $clazz->getConstructor()->getNumberOfRequiredParameters() >= 1) {
457
                    $nestedElement = new $classname(Phing::getCurrentProject() ?? $project);
458
                } else {
459
                    $nestedElement = new $classname();
460
                }
461
462
                if ($nestedElement instanceof Task && $element instanceof Task) {
463
                    $nestedElement->setOwningTarget($element->getOwningTarget());
464
                }
465
466
                $method->invoke($element, $nestedElement);
467
            } catch (Exception $exc) {
468
                throw new BuildException($exc->getMessage(), $exc);
469
            }
470
        } elseif ($this->bean->implementsInterface(CustomChildCreator::class)) {
471
            $method = $this->bean->getMethod('customChildCreator');
472
473
            try {
474
                $nestedElement = $method->invoke($element, strtolower($elementName), $project);
475
            } catch (Exception $exc) {
476
                throw new BuildException($exc->getMessage(), $exc);
477
            }
478
        } else {
479
            //try the add method for the element's parent class
480
            $typedefs = $project->getDataTypeDefinitions();
481
            if (isset($typedefs[$elementName])) {
482
                $elementClass = Phing::import($typedefs[$elementName]);
483
                $parentClass = get_parent_class($elementClass);
484
                $addMethod = 'add' . strtolower($parentClass);
485
486
                if (isset($this->nestedCreators[$addMethod])) {
487
                    $method = $this->nestedCreators[$addMethod];
488
489
                    try {
490
                        $project->log(
491
                            '    -calling parent adder '
492
                            . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
493
                            Project::MSG_DEBUG
494
                        );
495
                        $nestedElement = new $elementClass();
496
                        $method->invoke($element, $nestedElement);
497
                    } catch (Exception $exc) {
498
                        throw new BuildException($exc->getMessage(), $exc);
499
                    }
500
                }
501
            }
502
            if (null === $nestedElement) {
503
                $msg = $this->getElementName($project, $element) . " doesn't support the '{$elementName}' creator/adder.";
504
505
                throw new BuildException($msg);
506
            }
507
        }
508
509
        if ($nestedElement instanceof ProjectComponent) {
510
            $nestedElement->setProject($project);
511
        }
512
513
        return $nestedElement;
514
    }
515
516
    /**
517
     * Creates a named nested element.
518
     *
519
     * @param Project     $project
520
     * @param string      $element
521
     * @param string      $child
522
     * @param null|string $elementName
523
     *
524
     * @throws BuildException
525
     */
526
    public function storeElement($project, $element, $child, $elementName = null)
527
    {
528
        if (null === $elementName) {
529
            return;
530
        }
531
532
        $storer = 'addconfigured' . strtolower($elementName);
533
534
        if (isset($this->nestedStorers[$storer])) {
535
            $method = $this->nestedStorers[$storer];
536
537
            try {
538
                $project->log(
539
                    '    -calling storer ' . $method->getDeclaringClass()->getName() . '::' . $method->getName() . '()',
540
                    Project::MSG_DEBUG
541
                );
542
                $method->invoke($element, $child);
543
            } catch (Exception $exc) {
544
                throw new BuildException($exc->getMessage(), $exc);
545
            }
546
        }
547
    }
548
549
    /**
550
     * Does the introspected class support PCDATA?
551
     *
552
     * @return bool
553
     */
554
    public function supportsCharacters()
555
    {
556
        return null !== $this->methodAddText;
557
    }
558
559
    /**
560
     * Return all attribues supported by the introspected class.
561
     *
562
     * @return string[]
563
     */
564
    public function getAttributes()
565
    {
566
        $attribs = [];
567
        foreach (array_keys($this->attributeSetters) as $setter) {
568
            $attribs[] = $this->getPropertyName($setter, 'set');
569
        }
570
571
        return $attribs;
572
    }
573
574
    /**
575
     * Return all nested elements supported by the introspected class.
576
     *
577
     * @return string[]
578
     */
579
    public function getNestedElements()
580
    {
581
        return $this->nestedTypes;
582
    }
583
584
    /**
585
     * Get the name for an element.
586
     * When possible the full classnam (phing.tasks.system.PropertyTask) will
587
     * be returned.  If not available (loaded in taskdefs or typedefs) then the
588
     * XML element name will be returned.
589
     *
590
     * @param object $element the Task or type element
591
     *
592
     * @return string fully qualified class name of element when possible
593
     */
594
    public function getElementName(Project $project, $element)
595
    {
596
        $taskdefs = $project->getTaskDefinitions();
597
        $typedefs = $project->getDataTypeDefinitions();
598
599
        // check if class of element is registered with project (tasks & types)
600
        // most element types don't have a getTag() method
601
        $elClass = get_class($element);
602
603
        if (!in_array('getTag', get_class_methods($elClass))) {
604
            // loop through taskdefs and typesdefs and see if the class name
605
            // matches (case-insensitive) any of the classes in there
606
            foreach (array_merge($taskdefs, $typedefs) as $elName => $class) {
607
                if (0 === strcasecmp($elClass, $class)) {
608
                    return $class;
609
                }
610
            }
611
612
            return "{$elClass} (unknown)";
613
        }
614
615
        // ->getTag() method does exist, so use it
616
        $elName = $element->getTag();
617
        if (isset($taskdefs[$elName])) {
618
            return $taskdefs[$elName];
619
        }
620
621
        if (isset($typedefs[$elName])) {
622
            return $typedefs[$elName];
623
        }
624
625
        return "{$elName} (unknown)";
626
    }
627
628
    /**
629
     * Extract the name of a property from a method name - subtracting  a given prefix.
630
     *
631
     * @param string $methodName
632
     * @param string $prefix
633
     *
634
     * @return string
635
     */
636
    public function getPropertyName($methodName, $prefix)
637
    {
638
        $start = strlen($prefix);
639
640
        return strtolower(substr($methodName, $start));
641
    }
642
643
    /**
644
     * Prints warning message to screen if -debug was used.
645
     *
646
     * @param string $msg
647
     */
648
    public function warn($msg)
649
    {
650
        if (Project::MSG_DEBUG === Phing::getMsgOutputLevel()) {
651
            echo '[IntrospectionHelper] ' . $msg . "\n";
652
        }
653
    }
654
655
    /**
656
     * @param \ReflectionParameter $parameter
657
     * @return mixed|null
658
     */
659
    private function getClassnameFromParameter(\ReflectionParameter $parameter)
660
    {
661
        /** @var ReflectionType $reflectionType */
662
        $reflectionType = $parameter->getType();
663
        if ($reflectionType instanceof \ReflectionNamedType) {
664
            return !$reflectionType->isBuiltin() ? $reflectionType->getName() : null;
665
        }
666
        return null;
667
    }
668
}
669