Skip to content

Commit

Permalink
refactor: move method
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammad-Alavi committed Feb 5, 2025
1 parent 0e37beb commit 7d2ff95
Show file tree
Hide file tree
Showing 19 changed files with 249 additions and 284 deletions.
115 changes: 8 additions & 107 deletions src/Abstract/Requests/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,6 @@ abstract class Request extends LaravelRequest
*/
protected array $decode = [];

/**
* Defining the URL parameters (`/stores/{slug}/items`) allows applying
* validation rules on them and allows accessing them like request data.
*
* For example, you can use the `exists` validation rule on the `slug` parameter.
* And you can access the `slug` parameter using `$request->slug`.
*
* @example ['slug']
*
* @var string[]
*/
protected array $urlParameters = [];

/**
* To be used mainly from unit tests.
*/
Expand Down Expand Up @@ -73,7 +60,7 @@ public static function injectData(array $parameters = [], User|null $user = null
*
* @param array<string, mixed> $properties
*
* @return $this
* @return static
*/
public function withUrlParameters(array $properties): static
{
Expand Down Expand Up @@ -104,16 +91,6 @@ public function getDecodeArray(): array
return $this->decode;
}

/**
* Get the URL parameters array.
*
* @return string[]
*/
public function getUrlParametersArray(): array
{
return $this->urlParameters;
}

/**
* check if a user has permission to perform an action.
* User can set multiple permissions (separated with "|") and if the user has
Expand Down Expand Up @@ -194,92 +171,16 @@ public function mapInput(array $fields): void

public function all($keys = null): array
{
$data = parent::all($keys);

$data = $this->mergeUrlParameters($data);

return $this->decodeHashedIds($data);
}

/**
* apply validation rules to the ID's in the URL, since Laravel
* doesn't validate them by default!
*
* Now you can use validation rules like this: `'id' => 'required|integer|exists:items,id'`
*/
protected function mergeUrlParameters(array $requestData): array
{
foreach ($this->urlParameters as $param) {
$requestData[$param] = $this->route($param);
}

return $requestData;
}

/**
* without decoding the encoded id's you won't be able to use
* validation features like `exists:table,id`.
*/
protected function decodeHashedIds(array $data): array
{
if ([] !== $this->decode && config('apiato.hash-id')) {
foreach ($this->decode as $key) {
$data = $this->decodeRecursive($data, explode('.', $key), $key);
}
}

return $data;
}

private function decodeRecursive($data, $keys, string $currentField): mixed
{
if (is_null($data)) {
return $data;
if ([] === $this->decode || !config('apiato.hash-id')) {
return parent::all($keys);
}

if (empty($keys)) {
if ($this->skipHashIdDecode($data)) {
return $data;
}

if (!is_string($data)) {
throw new \RuntimeException('String expected, got ' . gettype($data));
}

$decodedField = hashids()->tryDecode($data);

if (is_null($decodedField)) {
throw new \RuntimeException('ID (' . $currentField . ') is incorrect, consider using the hashed ID.');
}
$routeParams = is_null($this->route()) ? [] : $this->route()->parameters();

return $decodedField;
}

// take the first element from the field
$field = array_shift($keys);

if ('*' === $field) {
// process each field of the array (and go down one level!)
$fields = Arr::wrap($data);
foreach ($fields as $key => $value) {
$data[$key] = $this->decodeRecursive($value, $keys, $currentField . '[' . $key . ']');
}

return $data;
}

if (!array_key_exists($field, $data)) {
return $data;
}

$data[$field] = $this->decodeRecursive($data[$field], $keys, $field);

return $data;
}

public function skipHashIdDecode($field): bool
{
return empty($field);
return hashids()->decodeFields([
...parent::all($keys),
...$routeParams,
], $this->decode);
}

/**
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/create.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
// 'id',
];

protected array $urlParameters = [
// 'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/delete.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
'id',
];

protected array $urlParameters = [
'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/edit.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
'id',
];

protected array $urlParameters = [
'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/find.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
'id',
];

protected array $urlParameters = [
'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/generic.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
// 'id',
];

protected array $urlParameters = [
// 'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/list.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
// 'id',
];

protected array $urlParameters = [
// 'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/store.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
// 'id',
];

protected array $urlParameters = [
// 'id',
];

public function rules(): array
{
return [
Expand Down
4 changes: 0 additions & 4 deletions src/Generator/Stubs/requests/update.stub
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ class {{class-name}} extends ParentRequest
'id',
];

protected array $urlParameters = [
'id',
];

public function rules(): array
{
return [
Expand Down
34 changes: 34 additions & 0 deletions src/Support/HashidsManagerDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Apiato\Support;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Traits\Macroable;
use Vinkla\Hashids\HashidsManager;
Expand Down Expand Up @@ -87,6 +89,38 @@ public function decodeArray(array $hash): array
return array_map(fn ($id) => $this->decode($id), $hash);
}

/**
* without decoding the encoded id's you won't be able to use
* validation features like `exists:table,id`.
*/
public function decodeFields(array $source, array $keys): array
{
$flattened = Arr::dot($source);

foreach ($keys as $pattern) {
$flattened = collect($flattened)->mapWithKeys(function ($value, $dotKey) use ($pattern) {
if (Str::is($pattern, $dotKey)) {
if (empty($value)) {
return [$dotKey => $value];
}

if (!is_string($value)) {
throw new \RuntimeException("String expected, got " . gettype($value));
}

$decoded = hashids()->tryDecode($value);
if (is_null($decoded)) {
throw new \RuntimeException("ID ({$dotKey}) is incorrect, consider using the hashed ID.");
}
return [$dotKey => $decoded];
}
return [$dotKey => $value];
})->all();
}

return Arr::undot($flattened);
}

/**
* Dynamically pass method calls to the underlying resource.
*
Expand Down
22 changes: 22 additions & 0 deletions tests/Functional/Abstract/RequestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Apiato\Abstract\Requests\Request;

describe(class_basename(Request::class), function (): void {
beforeEach(function (): void {
config(['apiato.hash-id' => true]);
});

it('can decode specified ids', function (): void {
$bookId = hashids()->encode(5);
$result = $this->patchJson("v1/books/{$bookId}", [
'title' => 'New Title',
'author_id' => hashids()->encode(10),
'nested' => [
'id' => hashids()->encode(15),
],
]);

$result->assertCreated();
});
})->covers(Request::class);
Loading

0 comments on commit 7d2ff95

Please sign in to comment.