Passed
Push — master ( 20a3ba...08be1b )
by Siad
12:52
created

IntrospectionHelper::createElement()   D

Complexity

Conditions 18
Paths 97

Size

Total Lines 92
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 23.975

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 18
eloc 59
c 1
b 0
f 0
nc 97
nop 3
dl 0
loc 92
ccs 39
cts 53
cp 0.7358
crap 23.975
rs 4.8666

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

337
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
338 294
                            break;
339 503
                        case "path":
340 15
                            $value = new Path($project, $value);
341 15
                            break;
342 489
                        case "reference":
343
                            $value = new Reference($project, $value);
344
                            break;
345
                        // any other object params we want to support should go here ...
346
                    }
347
                } // if hint !== null
348
            } // if not setrefid
349
        } // if is slot-listener
350
351
        try {
352 684
            $project->log(
353 684
                "    -calling setter " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
354 684
                Project::MSG_DEBUG
355
            );
356 684
            $method->invoke($element, $value);
357 9
        } catch (Exception $exc) {
358 9
            throw new BuildException($exc->getMessage(), $exc);
359
        }
360 683
    }
361
362
    /**
363
     * Adds PCDATA areas.
364
     *
365
     * @param  Project $project
366
     * @param  string $element
367
     * @param  string $text
368
     * @throws BuildException
369
     */
370 69
    public function addText(Project $project, $element, $text)
371
    {
372 69
        if ($this->methodAddText === null) {
373 1
            $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 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

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