Reflection::parsePhpDoc()   F
last analyzed

Complexity

Conditions 21
Paths 640

Size

Total Lines 100
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 462

Importance

Changes 0
Metric Value
dl 0
loc 100
ccs 0
cts 68
cp 0
rs 2.3015
c 0
b 0
f 0
cc 21
eloc 52
nc 640
nop 2
crap 462

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
/**
4
 *
5
 * This file is part of the Apix Project.
6
 *
7
 * (c) Franck Cassedanne <franck at ouarz.net>
8
 *
9
 * @license     http://opensource.org/licenses/BSD-3-Clause  New BSD License
10
 *
11
 */
12
13
namespace Apix;
14
15
class Reflection
16
{
17
    /**
18
     * @var string
19
     */
20
    protected $prefix;
21
22
    /**
23
     * Constructor
24
     *
25
     * @param string|null $prefix [optional default:null]
26
     */
27
    public function __construct($prefix='null')
28
    {
29
        $this->prefix = $prefix;
30
    }
31
32
    /**
33
     * Returns prefix
34
     *
35
     * @return string
36
     */
37
    public function getPrefix()
38
    {
39
        return $this->prefix;
40
    }
41
42
    /**
43
     * Returns the PHPDoc string
44
     *
45
     * @param  \Reflection|string $mix A reflection object or a PHPDoc string
46
     * @return array
47
     */
48
    // public function getPhpDocString($mix)
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
49
    // {
50
    //     return $mix instanceOf \Reflector ? $mix->getDocComment() : $mix;
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
51
    // }
52
53
    /**
54
     * Extract PHPDOCs
55
     *
56
     * @param  \Reflection|string $mix       A reflection object or a PHPDoc string
57
     * @param  array|null         $requireds An array of param name that are required.
58
     * @return array
59
     */
60
    public static function parsePhpDoc($mix, array $requireds=null)
61
    {
62
        if ($mix instanceof \Reflector) {
63
            $doc = $mix->getDocComment();
64
            $requireds = self::getRequiredParams($mix);
65
        } else {
66
            $doc = $mix;
67
        }
68
69
        $docs = array();
70
71
        // 1st - remove /*, *, */ from all the lines
72
        $doc = substr($doc, 3, -2);
73
74
        // 2. remove the carrier returns
75
        #$pattern = '/\r?\n *\* */';
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
76
77
        // does 1. + 2. BUT not too efficiently!
78
        #$pattern = '%(\r?\n(?! \* ?@))?^(/\*\*\r?\n \* | \*/| \* ?)%m';
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
79
80
        // same as 2. BUT keep the carrier returns in.
81
        // $pattern = '@(\r+|\t+)? *\* *@';
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
82
        $pattern = '@(\r+|\t+)? +\*\s?@';
83
84
        $str = preg_replace($pattern, '', $doc);
85
86
        #$lines =array_map('trim',explode(PHP_EOL, $str));
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
87
        $lines = preg_split('@\r?\n|\r@', $str, null, PREG_SPLIT_NO_EMPTY);
88
89
        // extract the short decription (title)
90
        $docs['title'] = array_shift($lines);
91
92
        // extract the long description
93
        $docs['description'] = '';
94
        foreach ($lines as $i => $line) {
95
            if (strlen(trim($line)) && strpos($line, '@') !== 0) {
96
                $docs['description'] .= $docs['description'] ? PHP_EOL . $line : $line;
97
                unset($lines[$i]);
98
            } else break;
99
        }
100
101
        // do all the "@entries"
102
        preg_match_all(
103
            '/@(?P<key>[\w_]+)\s+(?P<value>.*?)\s*(?=$|@[\w_]+\s)/s',
104
            $str,
105
            $lines
106
        );
107
108
        foreach ($lines['value'] as $i => $v) {
109
            $grp = $lines['key'][$i];
110
111
            if ($grp == 'param' || $grp == 'global') {
112
                // "@param string $param description of param"
113
                preg_match('/(?P<t>\S+)\s+\$(?P<name>\S+)(?P<d>\s+(?:[\w\W_\s]*))?/', $v, $m);
114
115
                $t = $grp == 'param' ? 'params' : 'globals';
116
                $docs[$t][$m['name']] = array(
117
                    'type'        => $m['t'],
118
                    'name'        => $m['name'],
119
                    'description' => isset($m['d'])
120
                                        ? trim($m['d'])
121
                                        : null,
122
                    'required'    => isset($requireds)
123
                                        && in_array($m['name'], $requireds)
124
                );
125
            } else {
126
                // other @entries as group
127
                $docs[$grp][] = $v;
128
            }
129
        }
130
131
        if (isset($docs['return'])) {
132
            $returns = array();
133
            foreach ($docs['return'] as $v) {
134
                // preg_match('/(?P<t>\S+)(?P<d>\s+(?:.+))?(?P<x>(?:.|\s)*)/', $v, $m); // with extar
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
135
                preg_match('/(?P<t>\S+)(?P<d>\s+(?:.|\s)*)/', $v, $m);
136
                if ( isset($m['t']) ) {
137
                    $returns[] = array(
138
                        'type'        => $m['t'],
139
                        'description' => trim($m['d']),
140
                        // 'extra'       => trim($m['x']),
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
141
                    );
142
                }
143
            }
144
            $docs['return'] = array( $returns );
145
        }
146
147
        // reduce group
148
        foreach ($docs as $key => $value) {
149
            if (
150
                $key !== 'params' && $key !== 'globals' && $key !== 'methods'
151
            ) {
152
                if (is_array($value) && count($value) == 1) {
153
                    $docs[$key] = reset( $docs[$key] );
154
                }
155
            }
156
        }
157
158
        return $docs;
159
    }
160
161
    /**
162
     * Returns the required parameters
163
     *
164
     * @param  \ReflectionFunctionAbstract $ref A reflected method/function to introspect
165
     * @return array                       The array of required parameters
166
     */
167
    public static function getRequiredParams(\ReflectionFunctionAbstract $ref)
168
    {
169
        $params = array();
170
        foreach ($ref->getParameters() as $param) {
171
            $name = $param->getName();
172
            if ( !$param->isOptional() ) {
173
                $params[] = $name;
174
            }
175
        }
176
177
        return $params;
178
    }
179
180
    /**
181
     * Extract source code
182
     *
183
     * @param  \Reflector $ref
184
     * @return array
185
     */
186
    public static function getSource(\Reflector $ref)
187
    {
188
        if( !file_exists( $ref->getFileName() ) ) return false;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getFileName() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by Apix\Reflection::getSource of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
189
190
        $start_offset = $ref->getStartLine();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getStartLine() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
191
        $end_offset   = $ref->getEndLine()-$start_offset;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getEndLine() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
192
193
        return join('',
194
            array_slice(
195
                file($ref->getFileName()),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Reflector as the method getFileName() does only exist in the following implementations of said interface: ReflectionClass, ReflectionFunction, ReflectionFunctionAbstract, ReflectionMethod, ReflectionObject.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
196
                $start_offset-1,
197
                $end_offset+1
198
            )
199
        );
200
    }
201
202
}
203