Completed
Push — master ( 9e0037...b2adab )
by Siad
12:57
created

IntrospectionHelper::setAttribute()   C

Complexity

Conditions 16
Paths 99

Size

Total Lines 91
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 22.354

Importance

Changes 0
Metric Value
cc 16
eloc 50
c 0
b 0
f 0
nc 99
nop 4
dl 0
loc 91
rs 5.5666
ccs 34
cts 48
cp 0.7083
crap 22.354

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 633
    public static function getHelper($class)
105
    {
106 633
        if (!isset(self::$helpers[$class])) {
107 143
            self::$helpers[$class] = new IntrospectionHelper($class);
108
        }
109
110 633
        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 143
    public function __construct($class)
125
    {
126 143
        $this->bean = new ReflectionClass($class);
127
128
        //$methods = get_class_methods($bean);
129 143
        foreach ($this->bean->getMethods() as $method) {
130 143
            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 143
                $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 143
                    $name === "setlocation"
142 143
                    || $name === "settasktype"
143 143
                    || ('addtask' === $name
144 2
                    && $this->isContainer()
145 2
                    && count($method->getParameters()) === 1
146 2
                        && Task::class === $method->getParameters()[0])
147
                ) {
148 114
                    continue;
149
                }
150
151 143
                if ($name === "addtext") {
152 14
                    $this->methodAddText = $method;
153 143
                } 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 143
                } elseif (strpos($name, "set") === 0 && count($method->getParameters()) === 1) {
170 136
                    $this->attributeSetters[$name] = $method;
171 143
                } 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 143
                } 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 143
                } 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 142
    }
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 613
    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 613
        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 613
            $as = "set" . strtolower($attributeName);
313
314 613
            if (!isset($this->attributeSetters[$as])) {
315 4
                if ($element instanceof DynamicAttribute) {
316 2
                    $element->setDynamicAttribute($attributeName, (string) $value);
317 2
                    return;
318
                }
319 2
                $msg = $this->getElementName($project, $element) . " doesn't support the '$attributeName' attribute.";
320 2
                throw new BuildException($msg);
321
            }
322
323 613
            $method = $this->attributeSetters[$as];
324
325 613
            if ($as == "setrefid") {
326 19
                $value = new Reference($project, $value);
327
            } else {
328 613
                $params = $method->getParameters();
329 613
                $reflectedAttr = null;
330
331
                // try to determine parameter type
332 613
                if (($argType = $params[0]->getType()) !== null) {
333
                    /** @var ReflectionNamedType $argType */
334 503
                    $reflectedAttr = $argType->getName();
335 585
                } elseif (($classType = $params[0]->getClass()) !== null) {
336
                    /** @var ReflectionClass $classType */
337
                    $reflectedAttr = $classType->getName();
338
                }
339
340
                // value is a string representation of a boolean type,
341
                // convert it to primitive
342 613
                if ($reflectedAttr === 'bool' || ($reflectedAttr !== 'string' && StringHelper::isBoolean($value))) {
343 242
                    $value = StringHelper::booleanValue($value);
344
                }
345
346
                // there should only be one param; we'll just assume ....
347 613
                if ($reflectedAttr !== null) {
348 503
                    switch (strtolower($reflectedAttr)) {
349 503
                        case "phingfile":
350 232
                            $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

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

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