From 5ef3f9450979a953060c1b64c4733baa761b9e7e Mon Sep 17 00:00:00 2001 From: SychO9 Date: Thu, 12 Nov 2020 14:24:42 +0100 Subject: [PATCH 01/17] Add ApiController Extender --- .../AbstractSerializeController.php | 147 ++++++++++++++++++ src/Api/Event/WillGetData.php | 3 + src/Api/Event/WillSerializeData.php | 3 + src/Extend/ApiController.php | 78 ++++++++++ 4 files changed, 231 insertions(+) create mode 100644 src/Extend/ApiController.php diff --git a/src/Api/Controller/AbstractSerializeController.php b/src/Api/Controller/AbstractSerializeController.php index 17338d55bf..52882c2ab2 100644 --- a/src/Api/Controller/AbstractSerializeController.php +++ b/src/Api/Controller/AbstractSerializeController.php @@ -14,6 +14,7 @@ use Flarum\Api\JsonApiResponse; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Support\Arr; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -82,6 +83,16 @@ abstract class AbstractSerializeController implements RequestHandlerInterface */ protected static $events; + /** + * @var array + */ + protected static $beforeDataCallbacks = []; + + /** + * @var array + */ + protected static $beforeSerializationCallbacks = []; + /** * {@inheritdoc} */ @@ -89,12 +100,30 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $document = new Document; + foreach (array_merge([static::class], class_parents($this)) as $class) { + if (isset(static::$beforeDataCallbacks[$class])) { + foreach (static::$beforeDataCallbacks as $callback) { + $callback($this); + } + } + } + + // Deprected in beta 15, removed in beta 16 static::$events->dispatch( new WillGetData($this) ); $data = $this->data($request, $document); + foreach (array_merge([static::class], class_parents($this)) as $class) { + if (isset(static::$beforeSerializationCallbacks[$class])) { + foreach (static::$beforeSerializationCallbacks as $callback) { + $data = array_merge($data, $callback($this, $data, $request, $document)); + } + } + } + + // Deprecated in beta 15, removed in beta 16 static::$events->dispatch( new WillSerializeData($this, $data, $request, $document) ); @@ -197,6 +226,106 @@ protected function buildParameters(ServerRequestInterface $request) return new Parameters($request->getQueryParams()); } + /** + * Set the serializer that will serialize data for the endpoint. + * + * @param string $serializer + */ + public function setSerializer($serializer) + { + $this->serializer = $serializer; + } + + /** + * Include the given relationship by default. + * + * @param string|array $name + */ + public function addInclude($name) + { + $this->include = array_merge($this->include, (array) $name); + } + + /** + * Don't include the given relationship by default. + * + * @param string $name + */ + public function removeInclude($name) + { + Arr::forget($this->include, $name); + } + + /** + * Make the given relationship available for inclusion. + * + * @param string $name + */ + public function addOptionalInclude($name) + { + $this->optionalInclude[] = $name; + } + + /** + * Don't allow the given relationship to be included. + * + * @param string $name + */ + public function removeOptionalInclude($name) + { + Arr::forget($this->optionalInclude, $name); + } + + /** + * Set the default number of results. + * + * @param int $limit + */ + public function setLimit($limit) + { + $this->limit = $limit; + } + + /** + * Set the maximum number of results. + * + * @param int $max + */ + public function setMaxLimit($max) + { + $this->maxLimit = $max; + } + + /** + * Allow sorting results by the given field. + * + * @param string $field + */ + public function addSortField($field) + { + $this->sortFields[] = $field; + } + + /** + * Disallow sorting results by the given field. + * + * @param string $field + */ + public function removeSortField($field) + { + Arr::forget($this->sortFields, $field); + } + + /** + * Set the default sort order for the results. + * + * @param array $sort + */ + public function setSort(array $sort) + { + $this->sort = $sort; + } + /** * @return Dispatcher */ @@ -228,4 +357,22 @@ public static function setContainer(Container $container) { static::$container = $container; } + + /** + * @param string $controllerClass + * @param callable $callback + */ + public static function addDataPreparationCallback(string $controllerClass, callable $callback) + { + static::$beforeDataCallbacks[$controllerClass][] = $callback; + } + + /** + * @param string $controllerClass + * @param callable $callback + */ + public static function addSerializationPreparationCallback(string $controllerClass, callable $callback) + { + static::$beforeSerializationCallbacks[$controllerClass][] = $callback; + } } diff --git a/src/Api/Event/WillGetData.php b/src/Api/Event/WillGetData.php index c6eb4f8592..973d77336e 100644 --- a/src/Api/Event/WillGetData.php +++ b/src/Api/Event/WillGetData.php @@ -12,6 +12,9 @@ use Flarum\Api\Controller\AbstractSerializeController; use Illuminate\Support\Arr; +/** + * @deprecated in beta 15, removed in beta 16 + */ class WillGetData { /** diff --git a/src/Api/Event/WillSerializeData.php b/src/Api/Event/WillSerializeData.php index d695fab52c..0cae29589f 100644 --- a/src/Api/Event/WillSerializeData.php +++ b/src/Api/Event/WillSerializeData.php @@ -13,6 +13,9 @@ use Psr\Http\Message\ServerRequestInterface; use Tobscure\JsonApi\Document; +/** + * @deprecated in beta 15, removed in beta 16 + */ class WillSerializeData { /** diff --git a/src/Extend/ApiController.php b/src/Extend/ApiController.php new file mode 100644 index 0000000000..6ce0609b12 --- /dev/null +++ b/src/Extend/ApiController.php @@ -0,0 +1,78 @@ +controllerClass = $controllerClass; + } + + /** + * @param callable|string $callback + * + * The callback can be a closure or an invokable class, and should accept: + * - $controller: An instance of this controller. + * + * @return self + */ + public function prepareDataQuery($callback) + { + $this->beforeDataCallbacks[] = $callback; + + return $this; + } + + /** + * @param $callback + * + * The callback can be a closure or an invokable class, and should accept: + * - $controller: An instance of this controller. + * - $data: An array of data. + * - $request: An instance of \Psr\Http\Message\ServerRequestInterface. + * - $document: An instance of \Tobscure\JsonApi\Document. + * + * The callable should return: + * - An array of additional data to merge with the existing array. + * Or a modified $data array. + * + * @return self + */ + public function prepareDataForSerialization($callback) + { + $this->beforeSerializationCallbacks[] = $callback; + + return $this; + } + + public function extend(Container $container, Extension $extension = null) + { + foreach ($this->beforeDataCallbacks as $beforeDataCallback) { + AbstractSerializeController::addDataPreparationCallback($this->controllerClass, $beforeDataCallback); + } + + foreach ($this->beforeSerializationCallbacks as $beforeSerializationCallback) { + AbstractSerializeController::addSerializationPreparationCallback($this->controllerClass, $beforeSerializationCallback); + } + } +} From 3936ba915cd9173be1c53e4f1faae80b60cc078d Mon Sep 17 00:00:00 2001 From: SychO9 Date: Sun, 15 Nov 2020 13:28:08 +0100 Subject: [PATCH 02/17] Make sure the priority for child classes is respected --- src/Api/Controller/AbstractSerializeController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Api/Controller/AbstractSerializeController.php b/src/Api/Controller/AbstractSerializeController.php index 52882c2ab2..c1eab13eb0 100644 --- a/src/Api/Controller/AbstractSerializeController.php +++ b/src/Api/Controller/AbstractSerializeController.php @@ -100,7 +100,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $document = new Document; - foreach (array_merge([static::class], class_parents($this)) as $class) { + foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { if (isset(static::$beforeDataCallbacks[$class])) { foreach (static::$beforeDataCallbacks as $callback) { $callback($this); @@ -115,7 +115,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $data = $this->data($request, $document); - foreach (array_merge([static::class], class_parents($this)) as $class) { + foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { if (isset(static::$beforeSerializationCallbacks[$class])) { foreach (static::$beforeSerializationCallbacks as $callback) { $data = array_merge($data, $callback($this, $data, $request, $document)); From e71c4fa141d2668ddf773b184b24f0efc8513208 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 17:58:55 +0100 Subject: [PATCH 03/17] Improve imported methods from WillGetData event --- .../AbstractSerializeController.php | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/Api/Controller/AbstractSerializeController.php b/src/Api/Controller/AbstractSerializeController.php index c1eab13eb0..2fabfcf935 100644 --- a/src/Api/Controller/AbstractSerializeController.php +++ b/src/Api/Controller/AbstractSerializeController.php @@ -14,7 +14,6 @@ use Flarum\Api\JsonApiResponse; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; -use Illuminate\Support\Arr; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; @@ -102,7 +101,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { if (isset(static::$beforeDataCallbacks[$class])) { - foreach (static::$beforeDataCallbacks as $callback) { + foreach (static::$beforeDataCallbacks[$class] as $callback) { $callback($this); } } @@ -117,7 +116,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { if (isset(static::$beforeSerializationCallbacks[$class])) { - foreach (static::$beforeSerializationCallbacks as $callback) { + foreach (static::$beforeSerializationCallbacks[$class] as $callback) { $data = array_merge($data, $callback($this, $data, $request, $document)); } } @@ -231,7 +230,7 @@ protected function buildParameters(ServerRequestInterface $request) * * @param string $serializer */ - public function setSerializer($serializer) + public function setSerializer(string $serializer) { $this->serializer = $serializer; } @@ -249,31 +248,31 @@ public function addInclude($name) /** * Don't include the given relationship by default. * - * @param string $name + * @param string|array $name */ public function removeInclude($name) { - Arr::forget($this->include, $name); + $this->include = array_diff($this->include, (array) $name); } /** * Make the given relationship available for inclusion. * - * @param string $name + * @param string|array $name */ public function addOptionalInclude($name) { - $this->optionalInclude[] = $name; + $this->optionalInclude = array_merge($this->optionalInclude, (array) $name); } /** * Don't allow the given relationship to be included. * - * @param string $name + * @param string|array $name */ public function removeOptionalInclude($name) { - Arr::forget($this->optionalInclude, $name); + $this->optionalInclude = array_diff($this->optionalInclude, (array) $name); } /** @@ -281,7 +280,7 @@ public function removeOptionalInclude($name) * * @param int $limit */ - public function setLimit($limit) + public function setLimit(int $limit) { $this->limit = $limit; } @@ -291,7 +290,7 @@ public function setLimit($limit) * * @param int $max */ - public function setMaxLimit($max) + public function setMaxLimit(int $max) { $this->maxLimit = $max; } @@ -299,21 +298,21 @@ public function setMaxLimit($max) /** * Allow sorting results by the given field. * - * @param string $field + * @param string|array $field */ public function addSortField($field) { - $this->sortFields[] = $field; + $this->sortFields = array_merge($this->sortFields, (array) $field); } /** * Disallow sorting results by the given field. * - * @param string $field + * @param string|array $field */ public function removeSortField($field) { - Arr::forget($this->sortFields, $field); + $this->sortFields = array_diff($this->sortFields, (array) $field); } /** @@ -364,6 +363,10 @@ public static function setContainer(Container $container) */ public static function addDataPreparationCallback(string $controllerClass, callable $callback) { + if (! isset(static::$beforeDataCallbacks[$controllerClass])) { + static::$beforeDataCallbacks[$controllerClass] = []; + } + static::$beforeDataCallbacks[$controllerClass][] = $callback; } @@ -373,6 +376,10 @@ public static function addDataPreparationCallback(string $controllerClass, calla */ public static function addSerializationPreparationCallback(string $controllerClass, callable $callback) { + if (! isset(static::$beforeSerializationCallbacks[$controllerClass])) { + static::$beforeSerializationCallbacks[$controllerClass] = []; + } + static::$beforeSerializationCallbacks[$controllerClass][] = $callback; } } From 32c1fbd2d5fa1638229fb8ba7579cc9782c989bd Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 18:37:16 +0100 Subject: [PATCH 04/17] Add more extender methods --- src/Extend/ApiController.php | 224 ++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 1 deletion(-) diff --git a/src/Extend/ApiController.php b/src/Extend/ApiController.php index 6ce0609b12..9e539631f8 100644 --- a/src/Extend/ApiController.php +++ b/src/Extend/ApiController.php @@ -11,6 +11,7 @@ use Flarum\Api\Controller\AbstractSerializeController; use Flarum\Extension\Extension; +use Flarum\Foundation\ContainerUtil; use Illuminate\Contracts\Container\Container; class ApiController implements ExtenderInterface @@ -18,6 +19,16 @@ class ApiController implements ExtenderInterface private $controllerClass; private $beforeDataCallbacks = []; private $beforeSerializationCallbacks = []; + private $serializer; + private $addIncludes = []; + private $removeIncludes = []; + private $addOptionalIncludes = []; + private $removeOptionalIncludes = []; + private $limit; + private $maxLimit; + private $addSortFields = []; + private $removeSortFields = []; + private $sort; /** * @param string $controllerClass The ::class attribute of the controller you are modifying. @@ -44,7 +55,7 @@ public function prepareDataQuery($callback) } /** - * @param $callback + * @param callable|string $callback * * The callback can be a closure or an invokable class, and should accept: * - $controller: An instance of this controller. @@ -65,8 +76,202 @@ public function prepareDataForSerialization($callback) return $this; } + /** + * Set the serializer that will serialize data for the endpoint. + * + * @param string $serializerClass + * @param callable|string|null $callback + * @return self + */ + public function setSerializer(string $serializerClass, $callback = null) + { + $this->serializer = [$serializerClass, $callback]; + + return $this; + } + + /** + * Include the given relationship by default. + * + * @param string|array $name + * @param callable|string|null $callback + * @return self + */ + public function addInclude($name, $callback = null) + { + $this->addIncludes[] = [$name, $callback]; + + return $this; + } + + /** + * Don't include the given relationship by default. + * + * @param string|array $name + * @param callable|string|null $callback + * @return self + */ + public function removeInclude($name, $callback = null) + { + $this->removeIncludes[] = [$name, $callback]; + + return $this; + } + + /** + * Make the given relationship available for inclusion. + * + * @param string|array $name + * @param callable|string|null $callback + * @return self + */ + public function addOptionalInclude($name, $callback = null) + { + $this->addOptionalIncludes[] = [$name, $callback]; + + return $this; + } + + /** + * Don't allow the given relationship to be included. + * + * @param string|array $name + * @param callable|string|null $callback + * @return self + */ + public function removeOptionalInclude($name, $callback = null) + { + $this->removeOptionalIncludes = [$name, $callback]; + + return $this; + } + + /** + * Set the default number of results. + * + * @param int $limit + * @param callable|string|null $callback + * @return self + */ + public function setLimit(int $limit, $callback = null) + { + $this->limit = [$limit, $callback]; + + return $this; + } + + /** + * Set the maximum number of results. + * + * @param int $max + * @param callable|string|null $callback + * @return self + */ + public function setMaxLimit(int $max, $callback = null) + { + $this->maxLimit = [$max, $callback]; + + return $this; + } + + /** + * Allow sorting results by the given field. + * + * @param string|array $field + * @param callable|string|null $callback + * @return self + */ + public function addSortField($field, $callback = null) + { + $this->addSortFields[] = [$field, $callback]; + + return $this; + } + + /** + * Disallow sorting results by the given field. + * + * @param string|array $field + * @param callable|string|null $callback + * @return self + */ + public function removeSortField($field, $callback = null) + { + $this->removeSortFields[] = [$field, $callback]; + + return $this; + } + + /** + * Set the default sort order for the results. + * + * @param array $sort + * @param callable|string|null $callback + * @return self + */ + public function setSort(array $sort, $callback = null) + { + $this->sort = [$sort, $callback]; + + return $this; + } + public function extend(Container $container, Extension $extension = null) { + $this->beforeDataCallbacks[] = function (AbstractSerializeController $controller) use ($container) { + if (isset($this->serializer) && $this->isApplicable($this->serializer[1], $controller, $container)) { + $controller->setSerializer($this->serializer[0]); + } + + foreach ($this->addIncludes as $addingInclude) { + if ($this->isApplicable($addingInclude[1], $controller, $container)) { + $controller->addInclude($addingInclude[0]); + } + } + + foreach ($this->removeIncludes as $removingInclude) { + if ($this->isApplicable($removingInclude[1], $controller, $container)) { + $controller->removeInclude($removingInclude[0]); + } + } + + foreach ($this->addOptionalIncludes as $addingOptionalInclude) { + if ($this->isApplicable($addingOptionalInclude[1], $controller, $container)) { + $controller->addOptionalInclude($addingOptionalInclude[0]); + } + } + + foreach ($this->removeOptionalIncludes as $removingOptionalInclude) { + if ($this->isApplicable($removingOptionalInclude[1], $controller, $container)) { + $controller->removeOptionalInclude($removingOptionalInclude[0]); + } + } + + foreach ($this->addSortFields as $addingSortField) { + if ($this->isApplicable($addingSortField[1], $controller, $container)) { + $controller->addSortField($addingSortField[0]); + } + } + + foreach ($this->removeSortFields as $removingSortField) { + if ($this->isApplicable($removingSortField[1], $controller, $container)) { + $controller->removeSortField($removingSortField[0]); + } + } + + if (isset($this->limit) && $this->isApplicable($this->limit[1], $controller, $container)) { + $controller->setLimit($this->limit[0]); + } + + if (isset($this->maxLimit) && $this->isApplicable($this->maxLimit[1], $controller, $container)) { + $controller->setMaxLimit($this->maxLimit[0]); + } + + if (isset($this->sort) && $this->isApplicable($this->sort[1], $controller, $container)) { + $controller->setSort($this->sort[0]); + } + }; + foreach ($this->beforeDataCallbacks as $beforeDataCallback) { AbstractSerializeController::addDataPreparationCallback($this->controllerClass, $beforeDataCallback); } @@ -75,4 +280,21 @@ public function extend(Container $container, Extension $extension = null) AbstractSerializeController::addSerializationPreparationCallback($this->controllerClass, $beforeSerializationCallback); } } + + /** + * @param callable|string|null $callback + * @param AbstractSerializeController $controller + * @param Container $container + * @return bool + */ + private function isApplicable($callback, AbstractSerializeController $controller, Container $container) + { + if (! isset($callback)) { + return true; + } + + $callback = ContainerUtil::wrapCallback($callback, $container); + + return (bool) $callback($controller); + } } From e6b383dd63f14b423f9d57f644244e82d75330f2 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 18:40:11 +0100 Subject: [PATCH 05/17] Allow using invokable classes --- src/Extend/ApiController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Extend/ApiController.php b/src/Extend/ApiController.php index 9e539631f8..bf5c794069 100644 --- a/src/Extend/ApiController.php +++ b/src/Extend/ApiController.php @@ -273,10 +273,12 @@ public function extend(Container $container, Extension $extension = null) }; foreach ($this->beforeDataCallbacks as $beforeDataCallback) { + $beforeDataCallback = ContainerUtil::wrapCallback($beforeDataCallback, $container); AbstractSerializeController::addDataPreparationCallback($this->controllerClass, $beforeDataCallback); } foreach ($this->beforeSerializationCallbacks as $beforeSerializationCallback) { + $beforeSerializationCallback = ContainerUtil::wrapCallback($beforeSerializationCallback, $container); AbstractSerializeController::addSerializationPreparationCallback($this->controllerClass, $beforeSerializationCallback); } } From aa9054224476cc6428dd7f77674e66fcdee2099a Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 21:00:05 +0100 Subject: [PATCH 06/17] data can be an object or an array --- src/Api/Controller/AbstractSerializeController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Api/Controller/AbstractSerializeController.php b/src/Api/Controller/AbstractSerializeController.php index 2fabfcf935..4be366e2db 100644 --- a/src/Api/Controller/AbstractSerializeController.php +++ b/src/Api/Controller/AbstractSerializeController.php @@ -117,7 +117,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) { if (isset(static::$beforeSerializationCallbacks[$class])) { foreach (static::$beforeSerializationCallbacks[$class] as $callback) { - $data = array_merge($data, $callback($this, $data, $request, $document)); + $callback($this, $data, $request, $document); } } } From 4ed4ec055764bd015bacef4778db604e84ea056f Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 21:05:32 +0100 Subject: [PATCH 07/17] Add ApiController extender tests --- .../extenders/ApiControllerTest.php | 432 ++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 tests/integration/extenders/ApiControllerTest.php diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php new file mode 100644 index 0000000000..4bd71c3e85 --- /dev/null +++ b/tests/integration/extenders/ApiControllerTest.php @@ -0,0 +1,432 @@ +prepareDatabase([ + 'users' => [ + $this->adminUser(), + $this->normalUser() + ], + 'discussions' => [ + ['id' => 1, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + ['id' => 2, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 3, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + ['id' => 3, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 1, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], + ], + ]); + } + + /** + * @test + */ + public function prepare_data_serialization_callback_works_if_added() + { + $this->extend( + (new Extend\ApiController(ShowDiscussionController::class)) + ->prepareDataForSerialization(function ($controller, Discussion $discussion) { + $discussion->title = 'dataSerializationPrepCustomTitle'; + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals('dataSerializationPrepCustomTitle', $payload['data']['attributes']['title']); + } + + /** + * @test + */ + public function prepare_data_serialization_callback_works_with_invokable_classes() + { + $this->extend( + (new Extend\ApiController(ShowDiscussionController::class)) + ->prepareDataForSerialization(CustomPrepareDataSerializationInvokableClass::class) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals(CustomPrepareDataSerializationInvokableClass::class, $payload['data']['attributes']['title']); + } + + /** + * @test + */ + public function custom_serializer_doesnt_work_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']); + } + + /** + * @test + */ + public function custom_serializer_works_if_set() + { + $this->extend( + (new Extend\ApiController(ShowDiscussionController::class)) + ->setSerializer(CustomDiscussionSerializer::class) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']); + } + + /** + * @test + */ + public function custom_serializer_works_if_set_with_invokable_class() + { + $this->extend( + (new Extend\ApiController(ShowPostController::class)) + ->setSerializer(CustomPostSerializer::class, CustomInvokableClass::class) + ); + + $this->prepDb(); + $this->prepareDatabase([ + 'posts' => [ + ['id' => 1, 'discussion_id' => 1, 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'type' => 'comment', 'content' => '

foo bar

'], + ], + ]); + + $response = $this->send( + $this->request('GET', '/api/posts/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']); + } + + /** + * @test + */ + public function custom_serializer_doesnt_work_with_false_callback_return() + { + $this->extend( + (new Extend\ApiController(ShowUserController::class)) + ->setSerializer(CustomUserSerializer::class, function () { + return false; + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']); + } + + /** + * @test + */ + public function custom_limit_doesnt_work_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertCount(3, $payload['data']); + } + + /** + * @test + */ + public function custom_limit_works_if_set() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->setLimit(1) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertCount(1, $payload['data']); + } + + /** + * @test + */ + public function custom_max_limit_works_if_set() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->setMaxLimit(1) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'page' => ['limit' => '5'], + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertCount(1, $payload['data']); + } + + /** + * @test + */ + public function custom_sort_field_doesnt_exist_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'sort' => 'userId', + ]) + ); + + $this->assertEquals(400, $response->getStatusCode()); + } + + /** + * @test + */ + public function custom_sort_field_doesnt_work_with_false_callback_return() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->addSortField('userId', function () { + return false; + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'sort' => 'userId', + ]) + ); + + $this->assertEquals(400, $response->getStatusCode()); + } + + /** + * @test + */ + public function custom_sort_field_exists_if_added() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->addSortField('userId') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'sort' => 'userId', + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(3, $payload['data'][0]['id']); + } + + /** + * @test + */ + public function custom_sort_field_exists_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'sort' => 'createdAt', + ]) + ); + + $this->assertEquals(200, $response->getStatusCode()); + } + + /** + * @test + */ + public function custom_sort_field_doesnt_exist_if_removed() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->removeSortField('createdAt') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'sort' => 'createdAt', + ]) + ); + + $this->assertEquals(400, $response->getStatusCode()); + } + + /** + * @test + */ + public function custom_sort_field_works_if_set() + { + $this->extend( + (new Extend\ApiController(ListDiscussionsController::class)) + ->addSortField('userId') + ->setSort(['userId' => 'desc']) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals(2, $payload['data'][0]['id']); + } +} + +class CustomDiscussionSerializer extends DiscussionSerializer +{ + protected function getDefaultAttributes($discussion) + { + return parent::getDefaultAttributes($discussion) + [ + 'customSerializer' => true + ]; + } +} + +class CustomUserSerializer extends UserSerializer +{ + protected function getDefaultAttributes($user) + { + return parent::getDefaultAttributes($user) + [ + 'customSerializer' => true + ]; + } +} + +class CustomPostSerializer extends PostSerializer +{ + protected function getDefaultAttributes($post) + { + return parent::getDefaultAttributes($post) + [ + 'customSerializer' => true + ]; + } +} + +class CustomInvokableClass +{ + public function __invoke() + { + return true; + } +} + +class CustomPrepareDataSerializationInvokableClass +{ + public function __invoke(ShowDiscussionController $controller, Discussion $discussion) + { + $discussion->title = __CLASS__; + } +} From 78e46b6c49748c27abc8311b76995fb911d57c22 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 27 Nov 2020 21:09:20 +0100 Subject: [PATCH 08/17] StyleCi Fixes --- tests/integration/extenders/ApiControllerTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 4bd71c3e85..10cf395de0 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -390,8 +390,8 @@ class CustomDiscussionSerializer extends DiscussionSerializer protected function getDefaultAttributes($discussion) { return parent::getDefaultAttributes($discussion) + [ - 'customSerializer' => true - ]; + 'customSerializer' => true + ]; } } @@ -400,8 +400,8 @@ class CustomUserSerializer extends UserSerializer protected function getDefaultAttributes($user) { return parent::getDefaultAttributes($user) + [ - 'customSerializer' => true - ]; + 'customSerializer' => true + ]; } } @@ -410,8 +410,8 @@ class CustomPostSerializer extends PostSerializer protected function getDefaultAttributes($post) { return parent::getDefaultAttributes($post) + [ - 'customSerializer' => true - ]; + 'customSerializer' => true + ]; } } From 51c036b68e285e28641de01ed4664ad0f9dbee1e Mon Sep 17 00:00:00 2001 From: SychO9 Date: Tue, 1 Dec 2020 15:37:19 +0100 Subject: [PATCH 09/17] Fix remove optional includes --- src/Extend/ApiController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Extend/ApiController.php b/src/Extend/ApiController.php index bf5c794069..a9f284547b 100644 --- a/src/Extend/ApiController.php +++ b/src/Extend/ApiController.php @@ -141,7 +141,7 @@ public function addOptionalInclude($name, $callback = null) */ public function removeOptionalInclude($name, $callback = null) { - $this->removeOptionalIncludes = [$name, $callback]; + $this->removeOptionalIncludes[] = [$name, $callback]; return $this; } From 10c20d05de5371a464af82e5fd1a515731acc739 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Tue, 1 Dec 2020 18:32:51 +0100 Subject: [PATCH 10/17] Add remaining relationship inclusion tests --- .../extenders/ApiControllerTest.php | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 10cf395de0..df6855ee94 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -21,6 +21,8 @@ use Flarum\Extend; use Flarum\Tests\integration\RetrievesAuthorizedUsers; use Flarum\Tests\integration\TestCase; +use Flarum\User\User; +use Illuminate\Support\Arr; class ApiControllerTest extends TestCase { @@ -33,6 +35,10 @@ protected function prepDb() $this->adminUser(), $this->normalUser() ], + 'groups' => [ + $this->adminGroup(), + $this->memberGroup() + ], 'discussions' => [ ['id' => 1, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 2, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], ['id' => 2, 'title' => 'Custom Discussion Title', 'created_at' => Carbon::now()->toDateTimeString(), 'user_id' => 3, 'first_post_id' => 0, 'comment_count' => 1, 'is_private' => 0], @@ -183,6 +189,150 @@ public function custom_serializer_doesnt_work_with_false_callback_return() $this->assertArrayNotHasKey('customSerializer', $payload['data']['attributes']); } + /** + * @test + */ + public function custom_relationship_not_included_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayNotHasKey('customApiControllerRelation', $payload['data']['relationships']); + $this->assertArrayNotHasKey('customApiControllerRelation2', $payload['data']['relationships']); + } + + /** + * @test + */ + public function custom_relationship_included_if_added() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasMany('customApiControllerRelation', Discussion::class, 'user_id'), + (new Extend\ApiSerializer(UserSerializer::class)) + ->hasMany('customApiControllerRelation', DiscussionSerializer::class), + (new Extend\ApiController(ShowUserController::class)) + ->addInclude('customApiControllerRelation') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customApiControllerRelation', $payload['data']['relationships']); + } + + /** + * @test + */ + public function custom_relationship_optionally_included_if_added() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'), + (new Extend\ApiSerializer(UserSerializer::class)) + ->hasMany('customApiControllerRelation2', DiscussionSerializer::class), + (new Extend\ApiController(ShowUserController::class)) + ->addOptionalInclude('customApiControllerRelation2') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'include' => 'customApiControllerRelation2', + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customApiControllerRelation2', $payload['data']['relationships']); + } + + /** + * @test + */ + public function custom_relationship_included_by_default() + { + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('groups', $payload['data']['relationships']); + } + + /** + * @test + */ + public function custom_relationship_not_included_if_removed() + { + $this->extend( + (new Extend\ApiController(ShowUserController::class)) + ->removeInclude('groups') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayNotHasKey('groups', Arr::get($payload, 'data.relationships', [])); + } + + /** + * @test + */ + public function custom_relationship_not_optionally_included_if_removed() + { + $this->extend( + (new Extend\Model(User::class)) + ->hasMany('customApiControllerRelation2', Discussion::class, 'user_id'), + (new Extend\ApiSerializer(UserSerializer::class)) + ->hasMany('customApiControllerRelation2', DiscussionSerializer::class), + (new Extend\ApiController(ShowUserController::class)) + ->addOptionalInclude('customApiControllerRelation2') + ->removeOptionalInclude('customApiControllerRelation2') + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/users/2', [ + 'authenticatedAs' => 1, + ])->withQueryParams([ + 'include' => 'customApiControllerRelation2', + ]) + ); + + $this->assertEquals(400, $response->getStatusCode()); + } + /** * @test */ From dd74792a32d00dd2f5139098099b084eacd756f3 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Tue, 1 Dec 2020 18:39:07 +0100 Subject: [PATCH 11/17] Fix ThrottleApiTest when tests are isolated --- tests/integration/extenders/ThrottleApiTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration/extenders/ThrottleApiTest.php b/tests/integration/extenders/ThrottleApiTest.php index ffa2886bde..79400cf402 100644 --- a/tests/integration/extenders/ThrottleApiTest.php +++ b/tests/integration/extenders/ThrottleApiTest.php @@ -85,6 +85,8 @@ public function false_overrides_true_for_evaluating_throttlers() $this->prepDb(); + $this->prepDb(); + $response = $this->send($this->request('GET', '/api/discussions', ['authenticatedAs' => 2])); $this->assertEquals(200, $response->getStatusCode()); From c247905740aa5e187ccad1b2648fae72773245e9 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Wed, 2 Dec 2020 12:35:52 +0100 Subject: [PATCH 12/17] Improve sorting tests --- tests/integration/extenders/ApiControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index df6855ee94..077f71f705 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -465,7 +465,7 @@ public function custom_sort_field_exists_if_added() $payload = json_decode($response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(3, $payload['data'][0]['id']); + $this->assertEquals([3, 1, 2], Arr::pluck($payload['data'], 'id')); } /** @@ -531,7 +531,7 @@ public function custom_sort_field_works_if_set() $payload = json_decode($response->getBody(), true); $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals(2, $payload['data'][0]['id']); + $this->assertEquals([2, 1, 3], Arr::pluck($payload['data'], 'id')); } } From 2da03765e32c5c89a5db4adb9ce63bdcc7ea1916 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Wed, 2 Dec 2020 13:13:44 +0100 Subject: [PATCH 13/17] Add more tests for class priority --- .../extenders/ApiControllerTest.php | 131 +++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 077f71f705..2b2825454b 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -10,6 +10,7 @@ namespace Flarum\Tests\integration\extenders; use Carbon\Carbon; +use Flarum\Api\Controller\AbstractShowController; use Flarum\Api\Controller\ListDiscussionsController; use Flarum\Api\Controller\ShowDiscussionController; use Flarum\Api\Controller\ShowPostController; @@ -95,6 +96,122 @@ public function prepare_data_serialization_callback_works_with_invokable_classes $this->assertEquals(CustomPrepareDataSerializationInvokableClass::class, $payload['data']['attributes']['title']); } + /** + * @test + */ + public function prepare_data_serialization_callback_works_if_added_to_parent_class() + { + $this->extend( + (new Extend\ApiController(AbstractShowController::class)) + ->prepareDataForSerialization(function ($controller, Discussion $discussion) { + if ($controller instanceof ShowDiscussionController) { + $discussion->title = 'dataSerializationPrepCustomTitle2'; + } + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals('dataSerializationPrepCustomTitle2', $payload['data']['attributes']['title']); + } + + /** + * @test + */ + public function prepare_data_serialization_callback_prioritizes_child_classes() + { + $this->extend( + (new Extend\ApiController(AbstractShowController::class)) + ->prepareDataForSerialization(function ($controller, Discussion $discussion) { + if ($controller instanceof ShowDiscussionController) { + $discussion->title = 'dataSerializationPrepCustomTitle3'; + } + }), + (new Extend\ApiController(ShowDiscussionController::class)) + ->prepareDataForSerialization(function ($controller, Discussion $discussion) { + $discussion->title = 'dataSerializationPrepCustomTitle4'; + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertEquals('dataSerializationPrepCustomTitle4', $payload['data']['attributes']['title']); + } + + /** + * @test + */ + public function prepare_data_query_callback_works_if_added_to_parent_class() + { + $this->extend( + (new Extend\ApiController(AbstractShowController::class)) + ->prepareDataQuery(function ($controller) { + if ($controller instanceof ShowDiscussionController) { + $controller->setSerializer(CustomDiscussionSerializer2::class); + } + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializer2', $payload['data']['attributes']); + } + + /** + * @test + */ + public function prepare_data_query_callback_prioritizes_child_classes() + { + $this->extend( + (new Extend\ApiController(AbstractShowController::class)) + ->prepareDataForSerialization(function ($controller) { + if ($controller instanceof ShowDiscussionController) { + $controller->setSerializer(CustomDiscussionSerializer2::class); + } + }), + (new Extend\ApiController(ShowDiscussionController::class)) + ->prepareDataForSerialization(function ($controller) { + $controller->setSerializer(CustomDiscussionSerializer::class); + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api/discussions/1', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('customSerializer', $payload['data']['attributes']); + } + /** * @test */ @@ -540,8 +657,18 @@ class CustomDiscussionSerializer extends DiscussionSerializer protected function getDefaultAttributes($discussion) { return parent::getDefaultAttributes($discussion) + [ - 'customSerializer' => true - ]; + 'customSerializer' => true + ]; + } +} + +class CustomDiscussionSerializer2 extends DiscussionSerializer +{ + protected function getDefaultAttributes($discussion) + { + return parent::getDefaultAttributes($discussion) + [ + 'customSerializer2' => true + ]; } } From 3497add7524e4b8a33369d29649b5cfce0ec3a1c Mon Sep 17 00:00:00 2001 From: SychO9 Date: Wed, 2 Dec 2020 13:17:25 +0100 Subject: [PATCH 14/17] StyleCi Fixes --- tests/integration/extenders/ApiControllerTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 2b2825454b..328a86217a 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -657,8 +657,8 @@ class CustomDiscussionSerializer extends DiscussionSerializer protected function getDefaultAttributes($discussion) { return parent::getDefaultAttributes($discussion) + [ - 'customSerializer' => true - ]; + 'customSerializer' => true + ]; } } @@ -667,8 +667,8 @@ class CustomDiscussionSerializer2 extends DiscussionSerializer protected function getDefaultAttributes($discussion) { return parent::getDefaultAttributes($discussion) + [ - 'customSerializer2' => true - ]; + 'customSerializer2' => true + ]; } } From ecb73fd32b6d40b57962075a802177e75e62805b Mon Sep 17 00:00:00 2001 From: SychO9 Date: Fri, 4 Dec 2020 15:46:35 +0100 Subject: [PATCH 15/17] Tweak docblocks --- src/Extend/ApiController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Extend/ApiController.php b/src/Extend/ApiController.php index a9f284547b..0551bb0250 100644 --- a/src/Extend/ApiController.php +++ b/src/Extend/ApiController.php @@ -59,7 +59,7 @@ public function prepareDataQuery($callback) * * The callback can be a closure or an invokable class, and should accept: * - $controller: An instance of this controller. - * - $data: An array of data. + * - $data: Mixed, can be an array of data or an object (like an instance of Collection or AbstractModel). * - $request: An instance of \Psr\Http\Message\ServerRequestInterface. * - $document: An instance of \Tobscure\JsonApi\Document. * From 27318fce53878cc251c5ccd9886ac4cce7b03e72 Mon Sep 17 00:00:00 2001 From: SychO9 Date: Sat, 5 Dec 2020 14:49:41 +0100 Subject: [PATCH 16/17] Add tests to ensure args can be passed by reference --- .../extenders/ApiControllerTest.php | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 328a86217a..078f43e9dc 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -13,9 +13,11 @@ use Flarum\Api\Controller\AbstractShowController; use Flarum\Api\Controller\ListDiscussionsController; use Flarum\Api\Controller\ShowDiscussionController; +use Flarum\Api\Controller\ShowForumController; use Flarum\Api\Controller\ShowPostController; use Flarum\Api\Controller\ShowUserController; use Flarum\Api\Serializer\DiscussionSerializer; +use Flarum\Api\Serializer\ForumSerializer; use Flarum\Api\Serializer\PostSerializer; use Flarum\Api\Serializer\UserSerializer; use Flarum\Discussion\Discussion; @@ -96,6 +98,60 @@ public function prepare_data_serialization_callback_works_with_invokable_classes $this->assertEquals(CustomPrepareDataSerializationInvokableClass::class, $payload['data']['attributes']['title']); } + /** + * @test + */ + public function prepare_data_serialization_allows_passing_args_by_reference_with_closures() + { + $this->extend( + (new Extend\ApiSerializer(ForumSerializer::class)) + ->hasMany('referenceTest', UserSerializer::class), + (new Extend\ApiController(ShowForumController::class)) + ->addInclude('referenceTest') + ->prepareDataForSerialization(function ($controller, &$data) { + $data['referenceTest'] = User::limit(2)->get(); + }) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('referenceTest', $payload['data']['relationships']); + } + + /** + * @test + */ + public function prepare_data_serialization_allows_passing_args_by_reference_with_invokable_classes() + { + $this->extend( + (new Extend\ApiSerializer(ForumSerializer::class)) + ->hasMany('referenceTest2', UserSerializer::class), + (new Extend\ApiController(ShowForumController::class)) + ->addInclude('referenceTest2') + ->prepareDataForSerialization(CustomInvokableClassArgsReference::class) + ); + + $this->prepDb(); + + $response = $this->send( + $this->request('GET', '/api', [ + 'authenticatedAs' => 1, + ]) + ); + + $payload = json_decode($response->getBody(), true); + + $this->assertArrayHasKey('referenceTest2', $payload['data']['relationships']); + } + /** * @test */ @@ -707,3 +763,11 @@ public function __invoke(ShowDiscussionController $controller, Discussion $discu $discussion->title = __CLASS__; } } + +class CustomInvokableClassArgsReference +{ + public function __invoke($controller, &$data) + { + $data['referenceTest2'] = User::limit(2)->get(); + } +} From c884006e50d1c7f8108fc66c9337cdbd2c7c8bcb Mon Sep 17 00:00:00 2001 From: SychO9 Date: Sat, 5 Dec 2020 14:58:39 +0100 Subject: [PATCH 17/17] Rename custom invokable class --- tests/integration/extenders/ApiControllerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/extenders/ApiControllerTest.php b/tests/integration/extenders/ApiControllerTest.php index 078f43e9dc..5d8784cd7c 100644 --- a/tests/integration/extenders/ApiControllerTest.php +++ b/tests/integration/extenders/ApiControllerTest.php @@ -316,7 +316,7 @@ public function custom_serializer_works_if_set_with_invokable_class() { $this->extend( (new Extend\ApiController(ShowPostController::class)) - ->setSerializer(CustomPostSerializer::class, CustomInvokableClass::class) + ->setSerializer(CustomPostSerializer::class, CustomApiControllerInvokableClass::class) ); $this->prepDb(); @@ -748,7 +748,7 @@ protected function getDefaultAttributes($post) } } -class CustomInvokableClass +class CustomApiControllerInvokableClass { public function __invoke() {