Passed
Pull Request — master (#32)
by
unknown
03:40
created

ErrorResponseBuilder::isSuccessResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Flugg\Responder\Http;
4
5
use InvalidArgumentException;
6
7
/**
8
 * This class represents an error response. An error response is responsible for translating
9
 * and resolving messages from error code and turning them into an error JSON response.
10
 *
11
 * @package flugger/laravel-responder
12
 * @author  Alexander Tømmerås <[email protected]>
13
 * @license The MIT License
14
 */
15
class ErrorResponseBuilder extends ResponseBuilder
16
{
17
    /**
18
     * Optional error data appended with the response.
19
     *
20
     * @var array
21
     */
22
    protected $data = [];
23
24
    /**
25
     * The error code used to identify the error.
26
     *
27
     * @var string
28
     */
29
    protected $errorCode;
30
31
    /**
32
     * A descriptive error message explaining what went wrong.
33
     *
34
     * @var string
35
     */
36
    protected $message;
37
38
    /**
39
     * Any parameters used to build the error message.
40
     *
41
     * @var array
42
     */
43
    protected $parameters = [];
44
45
    /**
46
     * The HTTP status code for the response.
47
     *
48
     * @var int
49
     */
50
    protected $statusCode = 500;
51
52
    /**
53
     * Translator service used for translating stuff.
54
     *
55
     * @var \Symfony\Component\Translation\TranslatorInterface|Illuminate\Contracts\Translation\Translator
56
     */
57
    protected $translator;
58
59
    /**
60
     * Constructor.
61
     *
62
     * @param \Illuminate\Contracts\Routing\ResponseFactory|\Laravel\Lumen\Http\ResponseFactory $responseFactory
63
     * @param \Symfony\Component\Translation\TranslatorInterface|Illuminate\Contracts\Translation\Translator $translator
64
     */
65
    public function __construct($responseFactory, $translator)
66
    {
67
        $this->translator = $translator;
68
69
        parent::__construct($responseFactory);
70
    }
71
72
    /**
73
     * Add additonal data appended to the error object.
74
     *
75
     * @param  array $data
76
     * @return self
77
     */
78
    public function addData(array $data):ErrorResponseBuilder
79
    {
80
        $this->data = array_merge($this->data, $data);
81
82
        return $this;
83
    }
84
85
    /**
86
     * Set the error code and optionally an error message.
87
     *
88
     * @param  mixed|null       $errorCode
89
     * @param  string|array|null $message
90
     * @return self
91
     */
92
    public function setError($errorCode = null, $message = null):ErrorResponseBuilder
93
    {
94
        $this->errorCode = $errorCode;
95
96
        if (is_array($message)) {
97
            $this->parameters = $message;
98
        } else {
99
            $this->message = $message;
100
        }
101
102
        return $this;
103
    }
104
105
    /**
106
     * Set the HTTP status code for the response.
107
     *
108
     * @param  int $statusCode
109
     * @return self
110
     * @throws \InvalidArgumentException
111
     */
112 View Code Duplication
    public function setStatus(int $statusCode):ResponseBuilder
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...
113
    {
114
        if ($statusCode < 400 || $statusCode >= 600) {
115
            throw new InvalidArgumentException("{$statusCode} is not a valid error HTTP status code.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $statusCode instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
116
        }
117
        return parent::setStatus($statusCode);
118
    }
119
120
    /**
121
     * Return response success flag as true
122
     *
123
     * @return bool
124
     */
125
    protected function isSuccessResponse():bool 
126
    {
127
        return false;
128
    }
129
130
    /**
131
     * Serialize the data and return as an array.
132
     *
133
     * @return array
134
     */
135
    public function toArray():array
136
    {
137
        return [
138
            'error' => $this->buildErrorData()
139
        ];
140
    }
141
142
    /**
143
     * Build the error object of the serialized response data.
144
     *
145
     * @return array|null
146
     */
147
    protected function buildErrorData()
148
    {
149
        if (is_null($this->errorCode)) {
150
            return null;
151
        }
152
153
        $data = [
154
            'code' => $this->errorCode,
155
            'message' => $this->message ?: $this->resolveMessage()
156
        ];
157
158
        return array_merge($data, $this->data);
159
    }
160
161
    /**
162
     * Resolve an error message from the translator.
163
     *
164
     * @return string|null
165
     */
166
    protected function resolveMessage()
167
    {
168
        if (! $this->translator->has($code = "errors.$this->errorCode")) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Translation\TranslatorInterface as the method has() does only exist in the following implementations of said interface: Illuminate\Translation\Translator.

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...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $this instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
169
            return null;
170
        }
171
172
        return $this->translator->trans($code, $this->parameters);
173
    }
174
}