Completed
Push — master ( 20b0ec...0fa80a )
by Siad
15:26
created

IntrospectionHelper::__construct()   D

Complexity

Conditions 29
Paths 30

Size

Total Lines 133
Code Lines 65

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 53
CRAP Score 32.3614

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 29
eloc 65
c 2
b 0
f 0
nc 30
nop 1
dl 0
loc 133
ccs 53
cts 63
cp 0.8413
crap 32.3614
rs 4.1666

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 617
    public static function getHelper($class)
105
    {
106 617
        if (!isset(self::$helpers[$class])) {
107 140
            self::$helpers[$class] = new IntrospectionHelper($class);
108
        }
109
110 617
        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 140
    public function __construct($class)
125
    {
126 140
        $this->bean = new ReflectionClass($class);
127
128
        //$methods = get_class_methods($bean);
129 140
        foreach ($this->bean->getMethods() as $method) {
130 140
            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 140
                $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 140
                    $name === "setlocation"
142 140
                    || $name === "settasktype"
143 140
                    || ('addtask' === $name
144 2
                    && $this->isContainer()
145 2
                    && count($method->getParameters()) === 1
146 2
                        && Task::class === $method->getParameters()[0])
147
                ) {
148 112
                    continue;
149
                }
150
151 140
                if ($name === "addtext") {
152 13
                    $this->methodAddText = $method;
153 140
                } 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 140
                } elseif (strpos($name, "set") === 0 && count($method->getParameters()) === 1) {
170 133
                    $this->attributeSetters[$name] = $method;
171 140
                } elseif (strpos($name, "create") === 0) {
172 48
                    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 47
                    if ($method->hasReturnType()) {
179 3
                        $this->nestedTypes[$name] = $method->getReturnType();
180
                    } else {
181 44
                        preg_match('/@return[\s]+([\w]+)/', $method->getDocComment(), $matches);
182 44
                        if (!empty($matches[1]) && class_exists($matches[1], false)) {
183 32
                            $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 27
                            $this->nestedTypes[$name] = $this->getPropertyName($name, "create");
188
                        }
189
                    }
190
191 47
                    $this->nestedCreators[$name] = $method;
192 140
                } 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 2
                    $classname = null;
211
212 2
                    if (($hint = $params[0]->getClass()) !== null) {
213 1
                        $classname = $hint->getName();
214
                    }
215
216 2
                    if ($classname === null) {
217 1
                        throw new BuildException(
218 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
219
                        );
220
                    }
221
222 1
                    $this->nestedTypes[$name] = $classname;
223
224 1
                    $this->nestedStorers[$name] = $method;
225 140
                } elseif (strpos($name, "add") === 0) {
226
                    // *must* use class hints if using add ...
227
228
                    // 1 param only
229 45
                    $params = $method->getParameters();
230 45
                    if (count($params) < 1) {
231
                        throw new BuildException(
232
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() must take at least one parameter."
233
                        );
234
                    }
235
236 45
                    if (count($params) > 1) {
237
                        $this->warn(
238
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() takes more than one parameter. (IH only uses the first)"
239
                        );
240
                    }
241
242 45
                    $classname = null;
243
244 45
                    if (($hint = $params[0]->getClass()) !== null) {
245 44
                        $classname = $hint->getName();
246
                    }
247
248
                    // we don't use the classname here, but we need to make sure it exists before
249
                    // we later try to instantiate a non-existent class
250 45
                    if ($classname === null) {
251 1
                        throw new BuildException(
252 1
                            $method->getDeclaringClass()->getName() . "::" . $method->getName() . "() method MUST use a class hint to indicate the class type of parameter."
253
                        );
254
                    }
255
256 44
                    $this->nestedCreators[$name] = $method;
257
                }
258
            } // if $method->isPublic()
259
        } // foreach
260 139
    }
261
262
    /**
263
     * Indicates whether the introspected class is a task container, supporting arbitrary nested tasks/types.
264
     *
265
     * @return bool true if the introspected class is a container; false otherwise.
266
     */
267 2
    public function isContainer()
268
    {
269 2
        return $this->bean->implementsInterface(TaskContainer::class);
270
    }
271
272
    /**
273
     * Sets the named attribute.
274
     *
275
     * @param  Project $project
276
     * @param  object $element
277
     * @param  string $attributeName
278
     * @param  mixed $value
279
     * @throws BuildException
280
     */
281 602
    public function setAttribute(Project $project, $element, $attributeName, &$value)
282
    {
283
        // we want to check whether the value we are setting looks like
284
        // a slot-listener variable:  %{task.current_file}
285
        //
286
        // slot-listener variables are not like properties, in that they cannot be mixed with
287
        // other text values.  The reason for this disparity is that properties are only
288
        // set when first constructing objects from XML, whereas slot-listeners are always dynamic.
289
        //
290
        // This is made possible by PHP5 (objects automatically passed by reference) and PHP's loose
291
        // typing.
292 602
        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...
293
            $as = "setlistening" . strtolower($attributeName);
294
295
            if (!isset($this->slotListeners[$as])) {
296
                $msg = $this->getElementName(
297
                    $project,
298
                    $element
299
                ) . " doesn't support a slot-listening '$attributeName' attribute.";
300
                throw new BuildException($msg);
301
            }
302
303
            $method = $this->slotListeners[$as];
304
305
            $key = StringHelper::slotVar($value);
306
            $value = Register::getSlot(
307
                $key
308
            ); // returns a RegisterSlot object which will hold current value of that register (accessible using getValue())
309
        } else {
310
            // Traditional value options
311
312 602
            $as = "set" . strtolower($attributeName);
313
314 602
            if (!isset($this->attributeSetters[$as])) {
315 2
                $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute.";
316 2
                throw new BuildException($msg);
317
            }
318
319 602
            $method = $this->attributeSetters[$as];
320
321 602
            if ($as == "setrefid") {
322 18
                $value = new Reference($project, $value);
323
            } else {
324 602
                $params = $method->getParameters();
325 602
                $reflectedAttr = null;
326
327
                // try to determine parameter type
328 602
                if (($argType = $params[0]->getType()) !== null) {
329
                    /** @var ReflectionNamedType $argType */
330 301
                    $reflectedAttr = $argType->getName();
331 592
                } elseif (($classType = $params[0]->getClass()) !== null) {
332
                    /** @var ReflectionClass $classType */
333
                    $reflectedAttr = $classType->getName();
334
                }
335
336
                // value is a string representation of a boolean type,
337
                // convert it to primitive
338 602
                if ($reflectedAttr === 'bool' || ($reflectedAttr !== 'string' && StringHelper::isBoolean($value))) {
339 246
                    $value = StringHelper::booleanValue($value);
340
                }
341
342
                // there should only be one param; we'll just assume ....
343 602
                if ($reflectedAttr !== null) {
344 301
                    switch (strtolower($reflectedAttr)) {
345 301
                        case "phingfile":
346 228
                            $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

346
                            $value = $project->resolveFile(/** @scrutinizer ignore-type */ $value);
Loading history...
347 228
                            break;
348 94
                        case "path":
349 15
                            $value = new Path($project, $value);
350 15
                            break;
351 80
                        case "reference":
352
                            $value = new Reference($project, $value);
353
                            break;
354
                        // any other object params we want to support should go here ...
355
                    }
356
                } // if hint !== null
357
            } // if not setrefid
358
        } // if is slot-listener
359
360
        try {
361 602
            $project->log(
362 602
                "    -calling setter " . $method->getDeclaringClass()->getName() . "::" . $method->getName() . "()",
363 602
                Project::MSG_DEBUG
364
            );
365 602
            $method->invoke($element, $value);
366 4
        } catch (Exception $exc) {
367 4
            throw new BuildException($exc->getMessage(), $exc);
368
        }
369 601
    }
370
371
    /**
372
     * Adds PCDATA areas.
373
     *
374
     * @param  Project $project
375
     * @param  string $element
376
     * @param  string $text
377
     * @throws BuildException
378
     */
379 57
    public function addText(Project $project, $element, $text)
380
    {
381 57
        if ($this->methodAddText === null) {
382 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

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