Skip to content

Commit d4395eb

Browse files
committed
feat(laravel-api-problem): add class, exception and http laravel api problem and stubs
1 parent 504d49a commit d4395eb

11 files changed

+579
-19
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pedrosalpr\LaravelApiProblem\Commands;
6+
7+
use Illuminate\Console\GeneratorCommand;
8+
9+
class LaravelApiProblemCommand extends GeneratorCommand
10+
{
11+
public $signature = 'laravel-api-problem:extend {name}';
12+
13+
public $description = 'Extend class api problem';
14+
15+
protected $type = 'LaravelApiProblem';
16+
17+
protected function getStub(): string
18+
{
19+
return $this->resolveStubPath('/Stubs/dummy.stub');
20+
}
21+
22+
protected function resolveStubPath(string $stub): string
23+
{
24+
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
25+
? $customPath
26+
: __DIR__ . $stub;
27+
}
28+
29+
protected function getDefaultNamespace($rootNamespace): string
30+
{
31+
return "{$rootNamespace}\\ApiProblem";
32+
}
33+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pedrosalpr\LaravelApiProblem\Commands;
6+
7+
use Illuminate\Console\GeneratorCommand;
8+
use Symfony\Component\Console\Input\InputArgument;
9+
10+
class LaravelApiProblemExceptionCommand extends GeneratorCommand
11+
{
12+
public $signature = 'laravel-api-problem:exception {name}';
13+
14+
public $description = 'Make class api problem exception';
15+
16+
protected $type = 'LaravelApiProblemException';
17+
18+
protected function getStub(): string
19+
{
20+
return $this->resolveStubPath('/Stubs/exception.stub');
21+
}
22+
23+
protected function resolveStubPath(string $stub): string
24+
{
25+
return file_exists($customPath = $this->laravel->basePath(trim($stub, '/')))
26+
? $customPath
27+
: __DIR__ . $stub;
28+
}
29+
30+
protected function getDefaultNamespace($rootNamespace): string
31+
{
32+
return "{$rootNamespace}\\Exceptions\\ApiProblem";
33+
}
34+
35+
protected function replaceClass($stub, $name)
36+
{
37+
$class = str_replace($this->getNamespace($name) . '\\', '', $name);
38+
39+
// Do string replacement
40+
return str_replace('{{ class }}', $class, $stub);
41+
}
42+
43+
protected function getArguments()
44+
{
45+
return [
46+
['name', InputArgument::REQUIRED, 'The name and root of the file.'],
47+
];
48+
}
49+
}

src/Commands/Stubs/dummy.stub

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace {{ namespace }};
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Response;
7+
use Pedrosalpr\LaravelApiProblem\Http\LaravelHttpApiProblem;
8+
use Pedrosalpr\LaravelApiProblem\LaravelApiProblem;
9+
10+
class {{ class }} extends LaravelApiProblem
11+
{
12+
public function __construct(
13+
protected \Throwable $exception,
14+
protected Request $request
15+
) {
16+
match (get_class($exception)) {
17+
\Exception::class => $this->dummy(),
18+
default => parent::__construct($exception, $request)
19+
};
20+
}
21+
22+
protected function dummy()
23+
{
24+
$extensions = [
25+
'errors' => "Dummy",
26+
];
27+
$this->apiProblem = new LaravelHttpApiProblem(
28+
Response::HTTP_I_AM_A_TEAPOT,
29+
$this->exception->getMessage(),
30+
$this->getUriInstance(),
31+
$extensions
32+
);
33+
}
34+
}

src/Commands/Stubs/exception.stub

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace {{ namespace }};
4+
5+
use Pedrosalpr\LaravelApiProblem\Exceptions\LaravelApiProblemException;
6+
7+
class {{ class }} extends LaravelApiProblemException
8+
{
9+
10+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pedrosalpr\LaravelApiProblem\Exceptions;
6+
7+
use Pedrosalpr\LaravelApiProblem\Http\LaravelHttpApiProblem;
8+
9+
class LaravelApiProblemException extends \Exception
10+
{
11+
public function __construct(
12+
protected int $statusCode,
13+
protected string $detail,
14+
protected string $instance,
15+
protected array $extensions = [],
16+
protected ?string $title = null,
17+
protected string $type = LaravelHttpApiProblem::TYPE_ABOUT_BLANK,
18+
int $code = 0,
19+
?\Throwable $previous = null,
20+
) {
21+
parent::__construct($detail, $code, $previous);
22+
}
23+
24+
public function getStatusCode(): int
25+
{
26+
return $this->statusCode;
27+
}
28+
29+
public function getDetail(): string
30+
{
31+
return $this->detail;
32+
}
33+
34+
public function getInstance(): string
35+
{
36+
return $this->instance;
37+
}
38+
39+
public function getExtensions(): array
40+
{
41+
return $this->extensions;
42+
}
43+
44+
public function getTitle(): ?string
45+
{
46+
return $this->title;
47+
}
48+
49+
public function getType(): string
50+
{
51+
return $this->type;
52+
}
53+
}

src/Http/LaravelHttpApiProblem.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pedrosalpr\LaravelApiProblem\Http;
6+
7+
use Illuminate\Support\Carbon;
8+
use Pedrosalpr\LaravelApiProblem\LaravelApiProblemInterface;
9+
10+
class LaravelHttpApiProblem implements LaravelApiProblemInterface
11+
{
12+
public const TYPE_ABOUT_BLANK = 'about:blank';
13+
14+
private const HEADER_PROBLEM_JSON = 'application/problem+json';
15+
16+
private static $statusTitles = [
17+
// CLIENT ERROR
18+
400 => 'Bad Request',
19+
401 => 'Unauthorized',
20+
402 => 'Payment Required',
21+
403 => 'Forbidden',
22+
404 => 'Not Found',
23+
405 => 'Method Not Allowed',
24+
406 => 'Not Acceptable',
25+
407 => 'Proxy Authentication Required',
26+
408 => 'Request Time-out',
27+
409 => 'Conflict',
28+
410 => 'Gone',
29+
411 => 'Length Required',
30+
412 => 'Precondition Failed',
31+
413 => 'Request Entity Too Large',
32+
414 => 'Request-URI Too Large',
33+
415 => 'Unsupported Media Type',
34+
416 => 'Requested range not satisfiable',
35+
417 => 'Expectation Failed',
36+
418 => 'I\'m a teapot',
37+
419 => 'Page Expired',
38+
422 => 'Unprocessable Entity',
39+
423 => 'Locked',
40+
424 => 'Failed Dependency',
41+
425 => 'Unordered Collection',
42+
426 => 'Upgrade Required',
43+
428 => 'Precondition Required',
44+
429 => 'Too Many Requests',
45+
431 => 'Request Header Fields Too Large',
46+
444 => 'Connection Closed Without Response',
47+
451 => 'Unavailable For Legal Reasons',
48+
499 => 'Client Closed Request',
49+
// SERVER ERROR
50+
500 => 'Internal Server Error',
51+
501 => 'Not Implemented',
52+
502 => 'Bad Gateway',
53+
503 => 'Service Unavailable',
54+
504 => 'Gateway Time-out',
55+
505 => 'HTTP Version not supported',
56+
506 => 'Variant Also Negotiates',
57+
507 => 'Insufficient Storage',
58+
508 => 'Loop Detected',
59+
511 => 'Network Authentication Required',
60+
];
61+
62+
private Carbon $timestamp;
63+
64+
public function __construct(
65+
private int $statusCode,
66+
private string $detail,
67+
private string $instance,
68+
private array $extensions = [],
69+
private ?string $title = null,
70+
private string $type = self::TYPE_ABOUT_BLANK
71+
) {
72+
if ($this->statusCode < 400 || $this->statusCode > 599) {
73+
$this->statusCode = 400;
74+
}
75+
if (!filter_var($this->type, FILTER_VALIDATE_URL) || empty($this->title)) {
76+
$this->title = $this->getTitleForStatusCode($this->statusCode);
77+
$this->type = self::TYPE_ABOUT_BLANK;
78+
}
79+
$this->timestamp = Carbon::now();
80+
}
81+
82+
public function getTitle(): string
83+
{
84+
return $this->title;
85+
}
86+
87+
public function getType(): string
88+
{
89+
return $this->type;
90+
}
91+
92+
public function getDetail(): string
93+
{
94+
return $this->detail;
95+
}
96+
97+
public function getStatusCode(): int
98+
{
99+
return $this->statusCode;
100+
}
101+
102+
public function getExtensions(): array
103+
{
104+
return $this->extensions;
105+
}
106+
107+
public function toArray(): array
108+
{
109+
return array_merge(
110+
[
111+
'status' => $this->statusCode,
112+
'type' => $this->type,
113+
'title' => $this->title,
114+
'detail' => $this->detail,
115+
'instance' => $this->instance,
116+
'timestamp' => $this->timestamp->toJSON()
117+
],
118+
$this->extensions
119+
);
120+
}
121+
122+
public function getHeaderProblemJson(): string
123+
{
124+
return self::HEADER_PROBLEM_JSON;
125+
}
126+
127+
private function getTitleForStatusCode(int $statusCode): string
128+
{
129+
return self::$statusTitles[$statusCode] ?? 'Unknown';
130+
}
131+
}

0 commit comments

Comments
 (0)