Completed
Push — master ( 8c9c54...6d4889 )
by Elf
44:15
created

Handler::unauthenticated()   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 2
crap 6
1
<?php
2
3
namespace ElfSundae\Support\Exceptions;
4
5
use Exception;
6
use Psr\Log\LoggerInterface;
7
use ElfSundae\Laravel\Api\ApiResponse;
8
use Illuminate\Auth\AuthenticationException;
9
use Illuminate\Validation\ValidationException;
10
use Symfony\Component\HttpKernel\Exception\HttpException;
11
use ElfSundae\Laravel\Api\Exceptions\ApiResponseException;
12
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
13
use Illuminate\Foundation\Http\Exceptions\MaintenanceModeException;
14
15
class Handler extends ExceptionHandler
16
{
17
    /**
18
     * Report or log an exception.
19
     *
20
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
21
     *
22
     * @param  \Exception  $e
23
     * @return void
24
     */
25
    public function report(Exception $e)
26
    {
27
        if ($this->shouldntReport($e)) {
28
            return;
29
        }
30
31
        try {
32
            $logger = $this->container->make(LoggerInterface::class);
33
        } catch (Exception $ex) {
34
            throw $e; // throw the original exception
35
        }
36
37
        $requestInfo = $this->getRequestInfo();
38
39
        $logger->error($e, $requestInfo);
40
41
        if (method_exists($this, 'notify')) {
42
            $this->notify($e, $requestInfo);
0 ignored issues
show
Bug introduced by
The method notify() does not seem to exist on object<ElfSundae\Support\Exceptions\Handler>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
43
        }
44
    }
45
46
    /**
47
     * Get the request info.
48
     *
49
     * @return array
50
     */
51
    protected function getRequestInfo()
52
    {
53
        $request = $this->container->make('request');
54
55
        $info = [
56
            'IP' => $request->ips(),
57
            'UserAgent' => $request->server('HTTP_USER_AGENT'),
58
            'URL' => $request->fullUrl(),
59
        ];
60
61
        if ($this->container->runningInConsole()) {
62
            $info['Command'] = implode(' ', $request->server('argv', []));
63
        }
64
65
        return $info;
66
    }
67
68
    /**
69
     * Render an exception into an HTTP response.
70
     *
71
     * @param  \Illuminate\Http\Request  $request
72
     * @param  \Exception  $e
73
     * @return \Illuminate\Http\Response
74
     */
75
    public function render($request, Exception $e)
76
    {
77
        if ($e instanceof MaintenanceModeException) {
78
            return $this->renderForMaintenanceMode($request, $e);
79
        } elseif ($e instanceof ApiResponseException) {
0 ignored issues
show
Bug introduced by
The class ElfSundae\Laravel\Api\Ex...ns\ApiResponseException does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
80
            return $e->getResponse();
81
        }
82
83
        return parent::render($request, $e);
84
    }
85
86
    /**
87
     * Render for MaintenanceModeException.
88
     *
89
     * @param  \Illuminate\Http\Request  $request
90
     * @param  \Illuminate\Foundation\Http\Exceptions\MaintenanceModeException  $e
91
     * @return \Illuminate\Http\Response
92
     */
93
    protected function renderForMaintenanceMode($request, MaintenanceModeException $e)
94
    {
95
        if ($request->expectsJson()) {
96
            return $this->createApiResponse($e->getMessage() ?: '服务器维护中,请稍后重试。', 503, $e);
97
        }
98
99
        return $this->toIlluminateResponse($this->renderHttpException($e), $e);
100
    }
101
102
    /**
103
     * Convert an authentication exception into an unauthenticated response.
104
     *
105
     * @param  \Illuminate\Http\Request  $request
106
     * @param  \Illuminate\Auth\AuthenticationException  $e
107
     * @return \Illuminate\Http\Response
108
     */
109
    protected function unauthenticated($request, AuthenticationException $e)
110
    {
111
        if ($request->expectsJson()) {
112
            return $this->createApiResponse('认证失败,请先登录。', 401, $e);
113
        }
114
115
        return redirect()->guest('login');
116
    }
117
118
    /**
119
     * Create a response object from the given validation exception.
120
     *
121
     * @param  \Illuminate\Validation\ValidationException  $e
122
     * @param  \Illuminate\Http\Request  $request
123
     * @return \Symfony\Component\HttpFoundation\Response
124
     */
125
    protected function convertValidationExceptionToResponse(ValidationException $e, $request)
126
    {
127
        if ($request->expectsJson()) {
128
            $message = implode("\n", array_flatten($e->validator->errors()->getMessages()));
129
130
            return $this->createApiResponse($message, 422, $e);
131
        }
132
133
        return parent::convertValidationExceptionToResponse($e, $request);
134
    }
135
136
    /**
137
     * Render the given HttpException.
138
     *
139
     * @param  \Symfony\Component\HttpKernel\Exception\HttpException  $e
140
     * @return \Symfony\Component\HttpFoundation\Response
141
     */
142
    protected function renderHttpException(HttpException $e)
143
    {
144
        if ($this->container->make('request')->expectsJson()) {
145
            $status = $e->getStatusCode();
146
            $message = $e->getMessage();
147
148
            if (empty($message)) {
149
                if (401 === $status) {
150
                    $message = '认证失败,请先登录';
151
                } elseif (403 === $status) {
152
                    $message = '无权操作,拒绝访问';
153
                } elseif (404 === $status) {
154
                    $message = '404 Not Found';
155
                } else {
156
                    $message = 'Error '.$status;
157
                }
158
            }
159
160
            return $this->createApiResponse($message, $status, $e);
161
        }
162
163
        return parent::renderHttpException($e);
164
    }
165
166
    /**
167
     * Create a Symfony response for the given exception.
168
     *
169
     * @param  \Exception  $e
170
     * @return \Symfony\Component\HttpFoundation\Response
171
     */
172
    protected function convertExceptionToResponse(Exception $e)
173
    {
174
        if (request()->expectsJson() && ! $this->container->make('config')->get('app.debug')) {
175
            return $this->convertExceptionToApiResponse($e);
176
        }
177
178
        return parent::convertExceptionToResponse($e);
179
    }
180
181
    /**
182
     * Create an API response for the given exception.
183
     *
184
     * @param  \Exception  $e
185
     * @return \ElfSundae\Laravel\Api\ApiResponse
186
     */
187
    protected function convertExceptionToApiResponse(Exception $e)
188
    {
189
        $code = $this->isHttpException($e) ? $e->getStatusCode() : $e->getCode();
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Exception as the method getStatusCode() does only exist in the following sub-classes of Exception: Illuminate\Foundation\Ht...aintenanceModeException, Illuminate\Http\Exceptions\PostTooLargeException, Symfony\Component\HttpKe...cessDeniedHttpException, Symfony\Component\HttpKe...BadRequestHttpException, Symfony\Component\HttpKe...n\ConflictHttpException, Symfony\Component\HttpKe...ption\GoneHttpException, Symfony\Component\HttpKe...Exception\HttpException, Symfony\Component\HttpKe...thRequiredHttpException, Symfony\Component\HttpKe...NotAllowedHttpException, Symfony\Component\HttpKe...AcceptableHttpException, Symfony\Component\HttpKe...n\NotFoundHttpException, Symfony\Component\HttpKe...tionFailedHttpException, Symfony\Component\HttpKe...onRequiredHttpException, Symfony\Component\HttpKe...navailableHttpException, Symfony\Component\HttpKe...nyRequestsHttpException, Symfony\Component\HttpKe...authorizedHttpException, Symfony\Component\HttpKe...ableEntityHttpException, Symfony\Component\HttpKe...dMediaTypeHttpException. 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...
190
191
        return $this->createApiResponse($e->getMessage(), $code, $e);
192
    }
193
194
    /**
195
     * Create an API response.
196
     *
197
     * @param  mixed  $message
198
     * @param  int  $code
199
     * @param  \Exception  $e
200
     * @return \ElfSundae\Laravel\Api\ApiResponse
201
     */
202
    protected function createApiResponse($message, $code, Exception $e)
203
    {
204
        $response = new ApiResponse($message, $code);
205
206
        if ($response->getCode() === $response::successCode()) {
207
            $response->setCode(-1 * $response->getCode());
208
        }
209
210
        if (empty($response->getMessage())) {
211
            $response->setMessage('Error #'.$response->getCode());
212
        }
213
214
        return $response->withException($e);
215
    }
216
}
217