Completed
Push — master ( f59019...093bae )
by Guillaume
04:06
created

Breaker::protect()   D

Complexity

Conditions 9
Paths 45

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 34
rs 4.909
cc 9
eloc 19
nc 45
nop 1
1
<?php
2
3
/*
4
 * This file is part of the circuit-breaker package
5
 *
6
 * Copyright (c) 2016 Guillaume Cavana
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * Feel free to edit as you please, and have fun.
12
 *
13
 * @author Guillaume Cavana <[email protected]>
14
 */
15
16
namespace Eljam\CircuitBreaker;
17
18
use Doctrine\Common\Cache\ArrayCache;
19
use Doctrine\Common\Cache\Cache;
20
use Eljam\CircuitBreaker\Event\CircuitEvent;
21
use Eljam\CircuitBreaker\Event\CircuitEvents;
22
use Eljam\CircuitBreaker\Exception\CircuitOpenException;
23
use Eljam\CircuitBreaker\Handler\Handler;
24
use Eljam\CircuitBreaker\Handler\HandlerInterface;
25
use Eljam\CircuitBreaker\Util\Utils;
26
use Symfony\Component\EventDispatcher\EventDispatcher;
27
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
28
use Symfony\Component\OptionsResolver\OptionsResolver;
29
30
/**
31
 * Class Breaker.
32
 */
33
class Breaker
34
{
35
    /**
36
     * $cache.
37
     *
38
     * @var Cache
39
     */
40
    protected $store;
41
42
    /**
43
     * $config.
44
     *
45
     * @var array
46
     */
47
    protected $config;
48
49
    /**
50
     * $handler.
51
     *
52
     * @var HandlerInterface
53
     */
54
    protected $handler;
55
56
    /**
57
     * $dispatcher.
58
     *
59
     * @var EventDispatcherInterface
60
     */
61
    protected $dispatcher;
62
63
    /**
64
     * Constructor.
65
     *
66
     * @param string                   $name
67
     * @param array                    $config
68
     * @param Cache                    $store
69
     * @param Handler                  $handler
70
     * @param EventDispatcherInterface $dispatcher
71
     */
72
    public function __construct(
73
        $name,
74
        array $config = [],
75
        Cache $store = null,
76
        HandlerInterface $handler = null,
77
        EventDispatcherInterface $dispatcher = null
78
    ) {
79
        $resolver = new OptionsResolver();
80
        $resolver->setDefaults([
81
            'max_failure' => 5,
82
            'reset_timeout' => 5,
83
            'exclude_exceptions' => [],
84
            'ignore_exceptions' => false,
85
        ]);
86
87
        $resolver->setAllowedTypes('exclude_exceptions', 'array');
88
        $resolver->setAllowedTypes('max_failure', 'int');
89
        $resolver->setAllowedTypes('reset_timeout', 'int');
90
91
        $this->config = $resolver->resolve($config);
92
        $this->store = null !== $store ? $store : (new ArrayCache());
93
        $this->handler = null !== $handler ? $handler($this->config) : new Handler($this->config);
94
        $this->dispatcher = null !== $dispatcher ? $dispatcher : new EventDispatcher();
95
        $name = Utils::snakeCase($name);
96
        $this->circuit = $this->loadCircuit($name);
0 ignored issues
show
Bug introduced by
The property circuit does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
97
    }
98
99
    /**
100
     * protect.
101
     *
102
     * @param \Closure $closure
103
     * @throw  \Exception
104
     *
105
     * @return mixed
106
     */
107
    public function protect(\Closure $closure)
108
    {
109
        try {
110
            $result = null;
111
            $circuitOpenException = null;
112
113
            if ($this->isClosed($this->circuit) || $this->isHalfOpen($this->circuit)) {
114
                $result = $closure();
115
                $this->success($this->circuit);
116
            } elseif ($this->isOpen($this->circuit)) {
117
                $circuitOpenException = new CircuitOpenException();
118
            }
119
        } catch (\Exception $e) {
120
            $this->failure($this->circuit);
121
122
            if (!$this->config['ignore_exceptions']) {
123
                if (!in_array(get_class($e), $this->config['exclude_exceptions'])) {
124
                    $result = $e;
125
                }
126
            }
127
        }
128
129
        // Throw circuit exception when it is opened
130
        if (null !== $circuitOpenException) {
131
            throw $circuitOpenException;
132
        }
133
134
        //Throw closure exception
135
        if ($result instanceof \Exception) {
136
            throw $result;
137
        }
138
139
        return $result;
140
    }
141
142
    /**
143
     * addListener.
144
     *
145
     * @param string         $eventName
146
     * @param \Closure|array $listener
147
     */
148
    public function addListener($eventName, $listener)
149
    {
150
        $this->dispatcher->addListener($eventName, $listener);
151
    }
152
153
    /**
154
     * isClosed.
155
     *
156
     * @param Circuit $circuit
157
     *
158
     * @return bool
159
     */
160 View Code Duplication
    protected function isClosed($circuit)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
    {
162
        if ($this->handler->isClosed($circuit)) {
0 ignored issues
show
Documentation introduced by
$circuit is of type object<Eljam\CircuitBreaker\Circuit>, but the function expects a object<Eljam\CircuitBreaker\Handler\Circuit>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
163
            $this->dispatcher->dispatch(CircuitEvents::CLOSED, (new CircuitEvent($circuit)));
164
165
            return true;
166
        }
167
168
        return;
169
    }
170
171
    /**
172
     * isOpen.
173
     *
174
     * @param Circuit $circuit
175
     *
176
     * @return bool
177
     */
178 View Code Duplication
    protected function isOpen($circuit)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
    {
180
        if ($this->handler->isOpen($circuit)) {
0 ignored issues
show
Documentation introduced by
$circuit is of type object<Eljam\CircuitBreaker\Circuit>, but the function expects a object<Eljam\CircuitBreaker\Handler\Circuit>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
181
            $this->dispatcher->dispatch(CircuitEvents::OPEN, (new CircuitEvent($circuit)));
182
183
            return true;
184
        }
185
186
        return;
187
    }
188
189
    /**
190
     * isHalfOpen.
191
     *
192
     * @param Circuit $circuit
193
     *
194
     * @return bool
195
     */
196 View Code Duplication
    protected function isHalfOpen($circuit)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
    {
198
        if ($this->handler->isHalfOpen($circuit)) {
0 ignored issues
show
Documentation introduced by
$circuit is of type object<Eljam\CircuitBreaker\Circuit>, but the function expects a object<Eljam\CircuitBreaker\Handler\Circuit>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
199
            $this->dispatcher->dispatch(CircuitEvents::HALF_OPEN, (new CircuitEvent($circuit)));
200
201
            return true;
202
        }
203
204
        return;
205
    }
206
207
    /**
208
     * success.
209
     *
210
     * @param Circuit $circuit
211
     */
212
    protected function success($circuit)
213
    {
214
        $circuit->resetFailture();
215
216
        $this->dispatcher->dispatch(CircuitEvents::SUCCESS, (new CircuitEvent($circuit)));
217
        $this->writeToStore($circuit);
218
    }
219
220
    /**
221
     * failure.
222
     *
223
     * @param Circuit $circuit
224
     */
225
    protected function failure(Circuit $circuit)
226
    {
227
        $circuit->incrementFailure();
228
        $circuit->setLastFailure(time());
229
230
        $this->dispatcher->dispatch(CircuitEvents::FAILURE, (new CircuitEvent($circuit)));
231
        $this->writeToStore($circuit);
232
    }
233
234
    /**
235
     * loadCircuit.
236
     *
237
     * @param string $name
238
     *
239
     * @return Circuit
240
     */
241
    protected function loadCircuit($name)
242
    {
243
        if ($this->store->contains($name)) {
244
            $circuit = $this->store->fetch($name);
245
        } else {
246
            $circuit = new Circuit($name);
247
        }
248
249
        $this->writeToStore($circuit);
250
251
        return $circuit;
252
    }
253
254
    protected function writeToStore(Circuit $circuit)
255
    {
256
        $this->store->save($circuit->getName(), $circuit);
257
    }
258
}
259