Completed
Pull Request — master (#5)
by Cees-Jan
02:52 queued 42s
created

AsyncTable::getDocBlock()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 7
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 6
1
<?php
2
3
namespace WyriHaximus\React\Cake\Orm;
4
5
use Cake\ORM\Query;
6
use Cake\ORM\TableRegistry;
7
use Doctrine\Common\Annotations\AnnotationReader;
8
use phpDocumentor\Reflection\DocBlock;
9
use phpDocumentor\Reflection\DocBlockFactory;
10
use React\Promise\PromiseInterface;
11
use WyriHaximus\React\Cake\Orm\Annotations\Async;
12
use WyriHaximus\React\Cake\Orm\Annotations\Sync;
13
14
trait AsyncTable
15
{
16
    /**
17
     * @var Pool
18
     */
19
    private $pool;
20
21
    /**
22
     * @var string
23
     */
24
    private $tableName;
25
26
    /**
27
     * @var AnnotationReader
28
     */
29
    private $annotationReader;
30
31
    /**
32
     * @var \ReflectionClass
33
     */
34
    private $reflectionClass;
35
36
    /**
37
     * @param Pool $pool
38
     * @param string $tableName
39
     */
40
    public function setUpAsyncTable(Pool $pool, $tableName, $tableClass)
41
    {
42
        $this->pool = $pool;
43
        $this->tableName = $tableName;
44
        $this->annotationReader = new AnnotationReader();
45
        $this->reflectionClass = new \ReflectionClass($tableClass);
46
    }
47
48
    /**
49
     * @param $function
50
     * @param array $arguments
51
     * @return PromiseInterface
52
     */
53
    protected function callAsyncOrSync($function, $arguments)
54
    {
55
        if ($this->pool === null) {
56
            return (new $this->tableName)->$function(...$arguments);
57
        }
58
59
        if (
60
            $this->returnsQuery($function) ||
61
            $this->hasMethodAnnotation($function, Async::class) ||
62
            (
63
                $this->hasClassAnnotation(Async::class) &&
64
                $this->hasNoMethodAnnotation($function)
65
            ) ||
66
            strpos(strtolower($function), 'save') === 0 ||
67
            strpos(strtolower($function), 'find') === 0 ||
68
            strpos(strtolower($function), 'fetch') === 0 ||
69
            strpos(strtolower($function), 'retrieve') === 0
70
        ) {
71
            return $this->callAsync($function, $arguments);
72
        }
73
74
        return $this->callSync($function, $arguments);
75
    }
76
77
    /**
78
     * @param $function
79
     * @param array $arguments
80
     * @return PromiseInterface
81
     */
82
    private function callSync($function, array $arguments = [])
83
    {
84
        $table = TableRegistry::get(md5($this->tableName), [
85
            'className' => $this->tableName,
86
            'table' => 'screenshots',
87
        ]);
88
        if (isset(class_uses($table)[TableRegistryTrait::class])) {
89
            $table->setRegistry(AsyncTableRegistry::class);
90
        }
91
        return \React\Promise\resolve(
92
            call_user_func_array(
93
                [
94
                    $table,
95
                    $function
96
                ],
97
                $arguments
98
            )
99
        );
100
    }
101
102
    /**
103
     * @param $function
104
     * @param array $arguments
105
     * @return PromiseInterface
106
     */
107
    private function callAsync($function, array $arguments = [])
108
    {
109
        $unSerialize = function ($input) {
110
            if (is_string($input)) {
111
                return unserialize($input);
112
            }
113
114
            return $input;
115
        };
116
        return $this->
117
        pool->
118
        call($this->tableName, $function, $arguments)->
119
        then($unSerialize, $unSerialize, $unSerialize);
120
    }
121
122
    /**
123
     * @param $class
124
     * @return bool
125
     */
126
    private function hasClassAnnotation($class)
127
    {
128
        return is_a($this->annotationReader->getClassAnnotation($this->reflectionClass, $class), $class);
129
    }
130
131
    /**
132
     * @param $method
133
     * @param $class
134
     * @return bool
135
     */
136
    private function hasMethodAnnotation($method, $class)
137
    {
138
        $methodReflection = $this->reflectionClass->getMethod($method);
139
        return is_a($this->annotationReader->getMethodAnnotation($methodReflection, $class), $class);
140
    }
141
142
    /**
143
     * @param $method
144
     * @return bool
145
     */
146
    private function hasNoMethodAnnotation($method)
147
    {
148
        $methodReflection = $this->reflectionClass->getMethod($method);
149
        return (
150
            $this->annotationReader->getMethodAnnotation($methodReflection, Async::class) === null &&
151
            $this->annotationReader->getMethodAnnotation($methodReflection, Sync::class) === null
152
        );
153
    }
154
155
    /**
156
     * @param $function
157
     * @return bool
158
     */
159
    private function returnsQuery($function)
160
    {
161
        $docBlockContents = $this->reflectionClass->getMethod($function)->getDocComment();
162
        if (!is_string($docBlockContents)) {
163
            return false;
164
        }
165
166
        $docBlock = $this->getDocBlock($docBlockContents);
167
        foreach ($docBlock->getTags() as $tag) {
168
            if ($tag->getName() === 'return' && ltrim($tag->getType(), '\\') == Query::class) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface phpDocumentor\Reflection\DocBlock\Tag as the method getType() does only exist in the following implementations of said interface: phpDocumentor\Reflection\DocBlock\Tags\Param, phpDocumentor\Reflection\DocBlock\Tags\Property, phpDocumentor\Reflection...Block\Tags\PropertyRead, phpDocumentor\Reflection...lock\Tags\PropertyWrite, phpDocumentor\Reflection\DocBlock\Tags\Return_, phpDocumentor\Reflection\DocBlock\Tags\Throws, phpDocumentor\Reflection\DocBlock\Tags\Var_.

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...
169
                return true;
170
            }
171
        }
172
173
        return false;
174
    }
175
176
    /**
177
     * @param $docBlockContents
178
     * @return DocBlock
179
     */
180
    private function getDocBlock($docBlockContents)
181
    {
182
        if (class_exists('phpDocumentor\Reflection\DocBlockFactory')) {
183
            return DocBlockFactory::createInstance()->create($docBlockContents);
184
        }
185
186
        return new DocBlock($docBlockContents);
187
    }
188
}
189