diff --git a/CHANGELOG.md b/CHANGELOG.md index 733ae0e8..2af08897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project aims to adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.9.0 (Thursday, 1 October, 2020) +### Changes +- Start database transaction much earlier and close it much later for ApiResource and Transformer strategies. Also set current route properly when resolving (https://github.com/knuckleswtf/scribe/pull/104) + ## 1.8.3 (Thursday, 17 September, 2020) ### Fixes - Reverts 1.8.2 as it broke a few things (https://github.com/knuckleswtf/scribe/commit/5a2217513945bcb92ca26e463f7717c0efb99ac1) diff --git a/docs/customization.md b/docs/customization.md index 532bcfc4..3319c1e0 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -67,12 +67,12 @@ In the template, you have the `$baseUrl` and `$route` variables available to you .. Important:: Parameters which have been excluded from the example requests (see `Specifying Example Values `_) will not be present in :code:`cleanQueryParameters`, :code:`cleanBodyParameters`, or :code:`fileParameters`. ``` +Finally, add the language to the `example_languages` array in your config and generate your documentation as usual. + ```eval_rst -.. Tip:: You can make use of some utilities in the class :code:`\Knuckles\Scribe\Tools\WritingUtils` to help you easily output data in various forms (such as in key=value format or as a PHP array). Please take a look at that class and its usages in the included example request templates for details. +.. Note:: Scribe makes use of CSS from [ighlight.js](https://highlightjs.org) for its syntax highlighting. The bundle we use only includes support for a bunch of the most popular languages. If your language isn't supported (all code appears white), you can [download a new CSS bundle](https://highlightjs.org/download/) yourself, and include your desired language. Then locate the highlight.js CSS file that Scribe outputs for you after generation, and replace that with the one you downloaded. ``` -Finally, add the language to the `example_languages` array in your config and generate your documentation as usual. - ## Customizing the code used in examples Customising existing language templates follows the same process as described above: publish assets, then modify the Blade templates in `resources/views/vendor/scribe/partials/example-requests` as necessary. @@ -124,4 +124,4 @@ There are also a number of included components that you can utilize in your Blad - `badges/base.blade.php`: The base badge component, used by `auth` and `http-method`. Takes in `colour` and `text` attributes, and uses Pastel's badge classes to create a badge. ## Changing the CSS styles -The CSS styling is provided by Pastel, which currently supports only one template. Consider making a pull request to add your alternative styles. +The CSS styling is provided by Pastel, which currently supports only one template. Consider making a pull request to add your alternative styles. In the meantime, you can manualy add CSS files to the generated output directory. diff --git a/src/Extracting/Strategies/Responses/UseApiResourceTags.php b/src/Extracting/Strategies/Responses/UseApiResourceTags.php index 4250527b..eca55f5f 100644 --- a/src/Extracting/Strategies/Responses/UseApiResourceTags.php +++ b/src/Extracting/Strategies/Responses/UseApiResourceTags.php @@ -49,11 +49,15 @@ public function __invoke(Route $route, ReflectionClass $controller, ReflectionFu $methodDocBlock = $docBlocks['method']; try { - return $this->getApiResourceResponse($methodDocBlock->getTags()); + $this->startDbTransaction(); + return $this->getApiResourceResponse($methodDocBlock->getTags(), $route); } catch (Exception $e) { c::warn('Exception thrown when fetching Eloquent API resource response for [' . implode(',', $route->methods) . "] {$route->uri}."); e::dumpExceptionIfVerbose($e); + return null; + } finally { + $this->endDbTransaction(); } } @@ -62,9 +66,11 @@ public function __invoke(Route $route, ReflectionClass $controller, ReflectionFu * * @param Tag[] $tags * + * @param \Illuminate\Routing\Route $route * @return array|null + * @throws \Exception */ - public function getApiResourceResponse(array $tags) + public function getApiResourceResponse(array $tags, Route $route) { if (empty($apiResourceTag = $this->getApiResourceTag($tags))) { return null; @@ -111,8 +117,11 @@ public function getApiResourceResponse(array $tags) : $apiResourceClass::collection($list); } + /** @var Response $response */ - $response = $resource->toResponse(app(Request::class)); + $response = $resource->toResponse(app(Request::class)->setRouteResolver(function () use ($route) { + return $route; + })); return [ [ @@ -171,7 +180,6 @@ private function getClassToBeTransformedAndAttributes(array $tags): array */ protected function instantiateApiResourceModel(string $type, array $factoryStates = [], array $relations = []) { - $this->startDbTransaction(); try { // Try Eloquent model factory @@ -181,7 +189,10 @@ protected function instantiateApiResourceModel(string $type, array $factoryState $factory = Utils::getModelFactory($type, $factoryStates); try { - return $factory->create(); + $model = $factory->create(); + $model->load($relations); + + return $model; } catch (Exception $e) { // If there was no working database, it would fail. return $factory->make(); @@ -204,8 +215,6 @@ protected function instantiateApiResourceModel(string $type, array $factoryState e::dumpExceptionIfVerbose($e); } } - } finally { - $this->endDbTransaction(); } return $instance; diff --git a/src/Extracting/Strategies/Responses/UseTransformerTags.php b/src/Extracting/Strategies/Responses/UseTransformerTags.php index c56b96cd..9fbdb7ad 100644 --- a/src/Extracting/Strategies/Responses/UseTransformerTags.php +++ b/src/Extracting/Strategies/Responses/UseTransformerTags.php @@ -47,12 +47,15 @@ public function __invoke(Route $route, ReflectionClass $controller, ReflectionFu $methodDocBlock = $docBlocks['method']; try { + $this->startDbTransaction(); return $this->getTransformerResponse($methodDocBlock->getTags()); } catch (Exception $e) { c::warn('Exception thrown when fetching transformer response for [' . implode(',', $route->methods) . "] {$route->uri}."); e::dumpExceptionIfVerbose($e); return null; + } finally { + $this->endDbTransaction(); } } @@ -160,7 +163,6 @@ private function getClassToBeTransformed(array $tags, ReflectionFunctionAbstract protected function instantiateTransformerModel(string $type, array $factoryStates = [], array $relations = []) { - $this->startDbTransaction(); try { // try Eloquent model factory @@ -193,8 +195,6 @@ protected function instantiateTransformerModel(string $type, array $factoryState e::dumpExceptionIfVerbose($e); } } - } finally { - $this->endDbTransaction(); } return $instance; diff --git a/tests/Fixtures/TestUser.php b/tests/Fixtures/TestUser.php index 8b9c8f32..386dbe52 100644 --- a/tests/Fixtures/TestUser.php +++ b/tests/Fixtures/TestUser.php @@ -6,6 +6,7 @@ class TestUser extends Model { + public function children() { return $this->hasMany(TestUser::class, 'parent_id'); diff --git a/tests/Fixtures/TestUserApiResource.php b/tests/Fixtures/TestUserApiResource.php index 1f79218f..7a0c57fa 100644 --- a/tests/Fixtures/TestUserApiResource.php +++ b/tests/Fixtures/TestUserApiResource.php @@ -24,6 +24,10 @@ public function toArray($request) }), ]; + if($request->route()->named('test')) { + $result['test'] = true; + } + if ($this['state1'] && $this['random-state']) { $result['state1'] = $this['state1']; $result['random-state'] = $this['random-state']; diff --git a/tests/Fixtures/TestUserApiResourceCollection.php b/tests/Fixtures/TestUserApiResourceCollection.php index 199a9901..b39f3869 100644 --- a/tests/Fixtures/TestUserApiResourceCollection.php +++ b/tests/Fixtures/TestUserApiResourceCollection.php @@ -15,11 +15,17 @@ class TestUserApiResourceCollection extends ResourceCollection */ public function toArray($request) { - return [ + $data = [ 'data' => $this->collection, 'links' => [ 'self' => 'link-value', ], ]; + + if($request->route()->named('test')) { + $data['test'] = true; + } + + return $data; } } diff --git a/tests/Strategies/Responses/UseApiResourceTagsTest.php b/tests/Strategies/Responses/UseApiResourceTagsTest.php index b87696c4..b17f0e25 100644 --- a/tests/Strategies/Responses/UseApiResourceTagsTest.php +++ b/tests/Strategies/Responses/UseApiResourceTagsTest.php @@ -2,11 +2,13 @@ namespace Knuckles\Scribe\Tests\Strategies\Responses; +use Illuminate\Routing\Route; use Knuckles\Scribe\Extracting\Strategies\Responses\UseApiResourceTags; use Knuckles\Scribe\ScribeServiceProvider; use Knuckles\Scribe\Tests\Fixtures\TestUser; use Knuckles\Scribe\Tools\DocumentationConfig; use Knuckles\Scribe\Tools\Utils; +use Mockery; use Mpociot\Reflection\DocBlock\Tag; use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts; use Orchestra\Testbench\TestCase; @@ -29,7 +31,7 @@ protected function getPackageProviders($app) return $providers; } - public function setUp(): void + public function setUp(): void { parent::setUp(); @@ -51,12 +53,18 @@ public function can_parse_apiresource_tags() { $config = new DocumentationConfig([]); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->once() + ->with('test') + ->andReturn(true); + $strategy = new UseApiResourceTags($config); $tags = [ new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'), ]; - $results = $strategy->getApiResourceResponse($tags); + $results = $strategy->getApiResourceResponse($tags, $route); $this->assertArraySubset([ [ @@ -66,6 +74,7 @@ public function can_parse_apiresource_tags() 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true ], ]), ], @@ -77,12 +86,18 @@ public function can_parse_apiresource_tags_with_model_factory_states() { $config = new DocumentationConfig([]); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->once() + ->with('test') + ->andReturn(true); + $strategy = new UseApiResourceTags($config); $tags = [ new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser states=state1,random-state'), ]; - $results = $strategy->getApiResourceResponse($tags); + $results = $strategy->getApiResourceResponse($tags, $route); $this->assertArraySubset([ [ @@ -92,6 +107,7 @@ public function can_parse_apiresource_tags_with_model_factory_states() 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true, 'state1' => true, 'random-state' => true, ], @@ -100,58 +116,121 @@ public function can_parse_apiresource_tags_with_model_factory_states() ], $results); } - /** @test */ - public function loads_specified_relations_for_model() - { - $factory = app(\Illuminate\Database\Eloquent\Factory::class); - $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) { - if ($user->id === 4) { - $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]); - $user->setRelation('children', collect([$child])); - } - }); + /** @test */ + public function loads_specified_relations_for_model() + { + $factory = app(\Illuminate\Database\Eloquent\Factory::class); + $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) { + if ($user->id === 4) { + $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]); + $user->setRelation('children', collect([$child])); + } + }); - $config = new DocumentationConfig([]); + $config = new DocumentationConfig([]); - $strategy = new UseApiResourceTags($config); - $tags = [ - new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), - new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'), - ]; - $results = $strategy->getApiResourceResponse($tags); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->times(2) + ->with('test') + ->andReturn(true); - $this->assertArraySubset([ - [ - 'status' => 200, - 'content' => json_encode([ - 'data' => [ - 'id' => 4, - 'name' => 'Tested Again', - 'email' => 'a@b.com', - 'children' => [ - [ - 'id' => 5, - 'name' => 'Tested Again', - 'email' => 'a@b.com', + $strategy = new UseApiResourceTags($config); + $tags = [ + new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), + new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'), + ]; + $results = $strategy->getApiResourceResponse($tags, $route); + + $this->assertArraySubset([ + [ + 'status' => 200, + 'content' => json_encode([ + 'data' => [ + 'id' => 4, + 'name' => 'Tested Again', + 'email' => 'a@b.com', + 'children' => [ + [ + 'id' => 5, + 'name' => 'Tested Again', + 'email' => 'a@b.com', + "test" => true + ], ], + "test" => true ], - ], - ]), - ], - ], $results); - } + ]), + ], + ], $results); + } + + /** @test */ + public function loads_specified_relations_for_generated_model() + { + $factory = app(\Illuminate\Database\Eloquent\Factory::class); + $factory->afterMaking(TestUser::class, function (TestUser $user, $faker) { + if ($user->id === 4) { + $child = Utils::getModelFactory(TestUser::class)->make(['id' => 5, 'parent_id' => 4]); + $user->setRelation('children', collect([$child])); + } + }); + $config = new DocumentationConfig([]); + + // Creating a mock route so we can test that the route is set properly during resolution + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->times(2) + ->with('test') + ->andReturn(true); + + $strategy = new UseApiResourceTags($config); + $tags = [ + new Tag('apiResource', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), + new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser with=children') + ]; + $results = $strategy->getApiResourceResponse($tags, $route); + + $this->assertArraySubset([ + [ + 'status' => 200, + 'content' => json_encode([ + 'data' => [ + 'id' => 4, + 'name' => 'Tested Again', + 'email' => 'a@b.com', + 'children' => [ + [ + 'id' => 5, + 'name' => 'Tested Again', + 'email' => 'a@b.com', + 'test' => true + ], + ], + 'test' => true + ], + ]), + ], + ], $results); + } /** @test */ public function can_parse_apiresourcecollection_tags() { $config = new DocumentationConfig([]); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->times(2) + ->with('test') + ->andReturn(true); + $strategy = new UseApiResourceTags($config); $tags = [ new Tag('apiResourceCollection', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResource'), new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'), ]; - $results = $strategy->getApiResourceResponse($tags); + $results = $strategy->getApiResourceResponse($tags, $route); $this->assertArraySubset([ [ @@ -162,11 +241,13 @@ public function can_parse_apiresourcecollection_tags() 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true, ], [ 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true, ], ], ]), @@ -179,12 +260,18 @@ public function can_parse_apiresourcecollection_tags_with_collection_class() { $config = new DocumentationConfig([]); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->times(3) + ->with('test') + ->andReturn(true); + $strategy = new UseApiResourceTags($config); $tags = [ - new Tag('apiResourceCollection', 'Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'), + new Tag('apiResourceCollection', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'), new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser'), ]; - $results = $strategy->getApiResourceResponse($tags); + $results = $strategy->getApiResourceResponse($tags, $route); $this->assertArraySubset([ [ @@ -195,16 +282,19 @@ public function can_parse_apiresourcecollection_tags_with_collection_class() 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true ], [ 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true ], ], 'links' => [ 'self' => 'link-value', ], + 'test' => true, ]), ], ], $results); @@ -215,12 +305,18 @@ public function can_parse_apiresourcecollection_tags_with_collection_class_and_p { $config = new DocumentationConfig([]); + $route = Mockery::mock(Route::class); + $route->shouldReceive('named') + ->times(2) + ->with('test') + ->andReturn(true); + $strategy = new UseApiResourceTags($config); $tags = [ - new Tag('apiResourceCollection', 'Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'), + new Tag('apiResourceCollection', '\Knuckles\Scribe\Tests\Fixtures\TestUserApiResourceCollection'), new Tag('apiResourceModel', '\Knuckles\Scribe\Tests\Fixtures\TestUser paginate=1,simple'), ]; - $results = $strategy->getApiResourceResponse($tags); + $results = $strategy->getApiResourceResponse($tags, $route); $this->assertArraySubset([ [ @@ -231,6 +327,7 @@ public function can_parse_apiresourcecollection_tags_with_collection_class_and_p 'id' => 4, 'name' => 'Tested Again', 'email' => 'a@b.com', + 'test' => true, ], ], 'links' => [ @@ -240,6 +337,7 @@ public function can_parse_apiresourcecollection_tags_with_collection_class_and_p "prev" => null, "next" => '/?page=2', ], + 'test' => true, "meta" => [ "current_page" => 1, "from" => 1,