Completed
Push — master ( b3e72f...7bead7 )
by Cees-Jan
10:16
created

Pool::paginate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 2
cts 2
cp 1
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 1
1
<?php declare(strict_types=1);
2
3
namespace WyriHaximus\React\Cake\Orm;
4
5
use Cake\Core\Configure;
6
use React\EventLoop\LoopInterface;
7
use React\EventLoop\Timer\TimerInterface;
8
use React\Promise\Deferred;
9
use React\Promise\PromiseInterface;
10
use WyriHaximus\React\ChildProcess\Messenger\Messages\Factory;
11
use WyriHaximus\React\ChildProcess\Pool\Factory\Flexible;
12
use WyriHaximus\React\ChildProcess\Pool\Options;
13
use WyriHaximus\React\ChildProcess\Pool\PoolInfoInterface;
14
use WyriHaximus\React\ChildProcess\Pool\PoolInterface;
15
use WyriHaximus\React\ChildProcess\Pool\PoolUtilizerInterface;
16
17
/**
18
 * Class Pool.
19
 * @package WyriHaximus\React\Cake\Orm
20
 */
21
class Pool implements PoolUtilizerInterface
22
{
23
    /**
24
     * @var LoopInterface
25
     */
26
    protected $loop;
27
28
    /**
29
     * @var PoolInfoInterface
30
     */
31
    protected $pool;
32
33
    /**
34
     * @var Pool
35
     */
36
    protected static $instance = null;
37
38
    /**
39
     * @var bool
40
     */
41
    protected static $reset = false;
42
43
    /**
44
     * @param LoopInterface $loop
45
     * @param array         $config
46
     */
47
    protected function __construct(LoopInterface $loop, array $config = [])
48 4
    {
49
        $this->loop = $loop;
50 4
51
        Flexible::createFromClass(
52 4
            WorkerChild::class,
53 4
            $this->loop,
54 4
            $this->applyConfig($config)
55
        )->then(function (PoolInterface $pool) {
56 4
            $this->pool = $pool;
57 4
        });
58
    }
59 4
60 4
    /**
61 4
     * @param  LoopInterface|null $loop
62
     * @param  array              $config
63
     * @throws \Exception
64
     * @return Pool
65
     */
66
    public static function getInstance(LoopInterface $loop = null, array $config = [])
67 4
    {
68
        if (null === self::$instance || self::$reset) {
69 4
            if (null === $loop) {
70 4
                throw new \Exception('Missing event loop');
71
            }
72
            self::$instance = new static($loop, $config);
73 4
            self::$reset = false;
74 4
        }
75
76
        return self::$instance;
77 4
    }
78
79
    public static function reset()
80
    {
81
        self::$reset = true;
82
    }
83
84
    /**
85
     * @param $className
86 4
     * @param $tableName
87
     * @param $function
88 4
     * @param  array            $arguments
89 4
     * @return PromiseInterface
90
     */
91
    public function call($className, $tableName, $function, array $arguments)
92 4
    {
93 4
        if ($this->pool instanceof PoolInterface) {
94
            return $this->poolCall($className, $tableName, $function, $arguments);
95
        }
96 4
97
        return $this->waitForPoolCall($className, $tableName, $function, $arguments);
0 ignored issues
show
Unused Code introduced by
The call to Pool::waitForPoolCall() has too many arguments starting with $arguments.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
98
    }
99 25
100
    public function paginate($tableName, $params, $settings)
101 25
    {
102 25
        return $this->pool->rpc(Factory::rpc('paginate', [
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WyriHaximus\React\ChildP...\Pool\PoolInfoInterface as the method rpc() does only exist in the following implementations of said interface: WyriHaximus\React\ChildProcess\Pool\Pool\Dummy, WyriHaximus\React\ChildProcess\Pool\Pool\Fixed, WyriHaximus\React\ChildProcess\Pool\Pool\Flexible.

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...
103
            'table' => $tableName,
104
            'params' => $params,
105
            'settings' => $settings,
106
        ]))->then(function ($result) {
107
            return \React\Promise\resolve($result['result']);
108
        });
109
    }
110
111
    /**
112
     * @inheritDoc
113
     */
114
    public function info()
115
    {
116
        if ($this->pool instanceof PoolInterface) {
117
            return $this->pool->info();
118
        }
119
120
        return [];
121
    }
122
123
    /**
124
     * @return LoopInterface
125
     */
126
    public function getLoop()
127
    {
128
        return $this->loop;
129
    }
130
131
    /**
132
     * @return PoolInfoInterface
133
     */
134
    public function getPool()
135
    {
136
        return $this->pool;
137
    }
138
139
    /**
140
     * @param  array $config
141
     * @return array
142
     */
143
    protected function applyConfig(array $config)
144
    {
145
        if (!isset($config['processOptions'])) {
146
            $config['processOptions'] = Configure::read('WyriHaximus.React.Cake.Orm.Line');
147
        }
148
149
        if (!isset($config[Options::TTL])) {
150
            $config[Options::TTL] = Configure::read('WyriHaximus.React.Cake.Orm.TTL');
151
        }
152
153
        return $config;
154
    }
155
156
    /**
157
     * @param $className
158
     * @param $tableName
159
     * @param $function
160
     * @param  array            $arguments
161
     * @return PromiseInterface
162
     */
163
    protected function poolCall($className, $tableName, $function, array $arguments)
164
    {
165
        return $this->pool->rpc(Factory::rpc('table.call', [
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface WyriHaximus\React\ChildP...\Pool\PoolInfoInterface as the method rpc() does only exist in the following implementations of said interface: WyriHaximus\React\ChildProcess\Pool\Pool\Dummy, WyriHaximus\React\ChildProcess\Pool\Pool\Fixed, WyriHaximus\React\ChildProcess\Pool\Pool\Flexible.

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...
166
            'className' => $className,
167
            'function' => $function,
168
            'table' => $tableName,
169
            'arguments' => serialize($arguments),
170
        ]))->then(function ($result) {
171
            return \React\Promise\resolve($result['result']);
172
        });
173
    }
174
175
    /**
176
     * @param $tableName
177 1
     * @param $function
178
     * @param  array            $arguments
179 1
     * @return PromiseInterface
180
     */
181
    protected function waitForPoolCall($tableName, $function, array $arguments)
182
    {
183
        $deferred = new Deferred();
184
185 1
        $this->loop->addPeriodicTimer(
186
            0.1,
187 1
            function (TimerInterface $timer) use ($deferred, $tableName, $function, $arguments) {
188
                if ($this->pool instanceof PoolInterface) {
189
                    $timer->cancel();
190
                    $deferred->resolve($this->call($tableName, $function, $arguments));
0 ignored issues
show
Bug introduced by
The call to call() misses a required argument $arguments.

This check looks for function calls that miss required arguments.

Loading history...
191
                }
192
            }
193
        );
194
195
        return $deferred->promise();
196
    }
197
}
198