Completed
Push — master ( 3cf178...59e65f )
by Ryosuke
03:33
created

StreamHandler::writeFunction()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 22
ccs 14
cts 14
cp 1
rs 8.6737
cc 5
eloc 14
nc 5
nop 2
crap 5
1
<?php
2
3
namespace mpyw\Cowitter\Components;
4
5
use mpyw\Co\Co;
6
use mpyw\Cowitter\Response;
7
use mpyw\Cowitter\Helpers\ResponseBodyDecoder;
8
9
class StreamHandler
10
{
11
    protected $headerResponse;
12
    protected $headerResponseBuffer = '';
13
    protected $headerResponseHandler;
14
    protected $eventBuffer = '';
15
    protected $eventHandler;
16
    protected $haltedByUser = false;
17
18 11
    public function __construct(callable $header_response_handler = null, callable $event_handler = null)
19 11
    {
20 11
        $this->headerResponseHandler = $header_response_handler;
21 11
        $this->eventHandler          = $event_handler;
22 11
    }
23
24 10
    public function headerFunction($ch, $str)
25 10
    {
26 10
        $handle = $this->headerResponseHandler;
27 10
        $this->headerResponseBuffer .= $str;
28 10
        if (substr($this->headerResponseBuffer, -4) === "\r\n\r\n") {
29 10
            $this->headerResponse = new Response($this->headerResponseBuffer, $ch);
30 10
            if ($handle) {
31 3
                (new \ReflectionFunction($handle))->isGenerator()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ReflectionFunction as the method isGenerator() does only exist in the following sub-classes of ReflectionFunction: Go\ParserReflection\ReflectionFunction. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
32 1
                ? Co::async($handle($this->headerResponse))
33 2
                : $handle($this->headerResponse);
34
            }
35
        }
36 10
        return strlen($str);
37
    }
38
39 8
    protected function processLine($ch, $line)
40 8
    {
41 8
        $handle = $this->eventHandler;
42 8
        if ('' === $line = rtrim($line)) {
43 5
            return;
44
        }
45 8
        $event = ResponseBodyDecoder::getDecodedResponse($this->headerResponse, $ch, $line);
46 8
        if ($handle) {
47 8
            if ((new \ReflectionFunction($handle))->isGenerator()) {
48 1
                Co::async(function () use ($handle, $event) {
49 1
                    if (false === (yield $handle($event->getContent()))) {
50 1
                        $this->haltedByUser = true;
51
                    }
52 1
                });
53 7
            } elseif (false === $handle($event->getContent())) {
54 5
                $this->haltedByUser = true;
55
            }
56
        }
57 8
    }
58
59 10
    public function writeFunction($ch, $str)
60 10
    {
61 10
        if ($this->haltedByUser) {
62
            // @codeCoverageIgnoreStart
63
            return 0;
64
            // @codeCoverageIgnoreEnd
65
        }
66 10
        $this->eventBuffer .= $str;
67 10
        if (200 !== $code = curl_getinfo($ch, CURLINFO_HTTP_CODE)) {
68 2
            ResponseBodyDecoder::getDecodedResponse($this->headerResponse, $ch, $this->eventBuffer);
69 1
            throw new \UnexpectedValueException('Unexpected response: ' . $this->eventBuffer);
70
        }
71 8
        while (false !== $pos = strpos($this->eventBuffer, "\n")) {
72 8
            $line = substr($this->eventBuffer, 0, $pos + 1);
73 8
            $this->eventBuffer = substr($this->eventBuffer, $pos + 1);
74 8
            $this->processLine($ch, $line);
75 8
            if ($this->haltedByUser) {
76 6
                return 0;
77
            }
78
        }
79 5
        return strlen($str);
80
    }
81
82 9
    public function isHaltedByUser()
83 9
    {
84 9
        return $this->haltedByUser;
85
    }
86
}
87