Skip to content

Commit 6d15e22

Browse files
authored
ci: guides documentation with doctrine search parameters (#6328)
* docs: how to run a local guide * docs: document parameter with doctrine filter
1 parent feea1b0 commit 6d15e22

File tree

8 files changed

+58
-38
lines changed

8 files changed

+58
-38
lines changed

docs/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
A guide is a PHP executable file that will be transformed into documentation. It follows [Diataxis How-To Guides](https://diataxis.fr/how-to-guides/) practice which is a must read before writing a guide.
66

7+
Read the "[How To Guide](./guides/how-to.php)" to understand how to write an API Platform guide.
8+
79
Guides are transformed to Markdown using [php-documentation-generator](https://github.com/php-documentation-generator/php-documentation-generator) which is merely a version of [docco](https://ashkenas.com/docco/) in PHP adapted to output markdown.
810

911
## WASM
@@ -15,3 +17,17 @@ docker run -v $(pwd):/src -v $(pwd)/public/php-wasm:/public -w /public php-wasm
1517
```
1618

1719
A build of [php-wasm](https://github.com/soyuka/php-wasm) is needed in the `public/php-wasm` directory to try it out.
20+
21+
## Local tests
22+
23+
First run `composer update`.
24+
25+
Then, get the [`pdg-phpunit`](https://github.com/php-documentation-generator/php-documentation-generator/tags) binary that allows to run single-file test.
26+
27+
Use `KERNEL_CLASS` and `PDG_AUTOLOAD` to run a guide:
28+
29+
```
30+
APP_DEBUG=0 \
31+
PDG_AUTOLOAD='vendor/autoload.php' \
32+
KERNEL_CLASS='\ApiPlatform\Playground\Kernel' pdg-phpunit guides/doctrine-search-filter.php
33+
```

docs/guides/create-a-custom-doctrine-filter.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ final class RegexpFilter extends AbstractFilter
3333
/*
3434
* Filtered properties is accessible through getProperties() method: property => strategy
3535
*/
36-
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
36+
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
3737
{
3838
/*
3939
* Otherwise this filter is applied to order and page as well.

docs/guides/doctrine-search-filter.php

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,18 @@
1212
// By default, all filters are disabled. They must be enabled explicitly.
1313

1414
namespace App\Entity {
15-
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
16-
use ApiPlatform\Metadata\ApiFilter;
17-
use ApiPlatform\Metadata\ApiResource;
15+
use ApiPlatform\Metadata\GetCollection;
16+
use ApiPlatform\Metadata\QueryParameter;
1817
use Doctrine\ORM\Mapping as ORM;
1918

20-
#[ApiResource]
21-
//
22-
// By using the `#[ApiFilter]` attribute, this attribute automatically declares the service,
23-
// and you just have to use the filter class you want.
24-
//
25-
// If the filter is declared on the resource, you can specify on which properties it applies.
26-
#[ApiFilter(SearchFilter::class, properties: ['title'])]
19+
#[GetCollection(
20+
uriTemplate: 'books{._format}',
21+
parameters: [
22+
// Declare a QueryParameter with the :property pattern that matches the properties declared on the Filter.
23+
// The filter is a service declared in the next class.
24+
':property' => new QueryParameter(filter: 'app.search_filter'),
25+
]
26+
)]
2727
#[ORM\Entity]
2828
class Book
2929
{
@@ -34,13 +34,25 @@ class Book
3434
public ?string $title = null;
3535

3636
#[ORM\Column]
37-
// We can also declare the filter attribute on a property and specify the strategy that should be used.
38-
// For a list of availabe options [head to the documentation](/docs/core/filters/#search-filter)
39-
#[ApiFilter(SearchFilter::class, strategy: 'partial')]
4037
public ?string $author = null;
4138
}
4239
}
4340

41+
namespace App\DependencyInjection {
42+
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
43+
44+
function configure(ContainerConfigurator $configurator): void
45+
{
46+
// This is the custom search filter we declare, if you prefer to use decoration, suffix the parent service with `.instance`. They implement the `PropertyAwareFilterInterface` that allows you to override a filter's property.
47+
$services = $configurator->services();
48+
$services->set('app.search_filter')
49+
->parent('api_platform.doctrine.orm.search_filter')
50+
// Search strategies may be defined here per properties, [read more](https://api-platform.com/docs/core/filters/) on the filter documentation.
51+
->args([['author' => 'partial', 'title' => 'partial']])
52+
->tag('api_platform.filter');
53+
}
54+
}
55+
4456
namespace App\Playground {
4557
use Symfony\Component\HttpFoundation\Request;
4658

@@ -85,8 +97,7 @@ public function load(ObjectManager $manager): void
8597
$bookFactory->many(10)->create(fn () => [
8698
'title' => faker()->name(),
8799
'author' => faker()->firstName(),
88-
]
89-
);
100+
]);
90101
}
91102
}
92103
}
@@ -100,36 +111,30 @@ final class BookTest extends ApiTestCase
100111
{
101112
use TestGuideTrait;
102113

103-
public function testAsAnonymousICanAccessTheDocumentation(): void
114+
public function testGetDocumentation(): void
104115
{
105116
static::createClient()->request('GET', '/books.jsonld');
106117

107118
$this->assertResponseIsSuccessful();
108-
$this->assertMatchesResourceCollectionJsonSchema(Book::class, '_api_/books{._format}_get_collection', 'jsonld');
119+
$this->assertMatchesResourceCollectionJsonSchema(Book::class, '_api_books{._format}_get_collection', 'jsonld');
109120
$this->assertJsonContains([
110121
'hydra:search' => [
111122
'@type' => 'hydra:IriTemplate',
112-
'hydra:template' => '/books.jsonld{?title,title[],author}',
123+
'hydra:template' => '/books.jsonld{?author,title}',
113124
'hydra:variableRepresentation' => 'BasicRepresentation',
114125
'hydra:mapping' => [
115126
[
116127
'@type' => 'IriTemplateMapping',
117-
'variable' => 'title',
118-
'property' => 'title',
128+
'variable' => 'author',
129+
'property' => 'author',
119130
'required' => false,
120131
],
121132
[
122133
'@type' => 'IriTemplateMapping',
123-
'variable' => 'title[]',
134+
'variable' => 'title',
124135
'property' => 'title',
125136
'required' => false,
126137
],
127-
[
128-
'@type' => 'IriTemplateMapping',
129-
'variable' => 'author',
130-
'property' => 'author',
131-
'required' => false,
132-
],
133138
],
134139
],
135140
]);

docs/guides/error-provider.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// rfc_7807_compliant_errors: true
1515
// ```
1616
// To customize the API Platform response, replace the api_platform.state.error_provider with your own provider:
17+
1718
namespace App\ApiResource {
1819
use ApiPlatform\Metadata\ApiResource;
1920
use ApiPlatform\Metadata\Get;
@@ -75,6 +76,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
7576

7677
// This is replacing the service, the "key" is important as this is the provider we
7778
// will look for when handling an exception.
79+
7880
namespace App\DependencyInjection {
7981
use App\State\ErrorProvider;
8082
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
@@ -86,10 +88,8 @@ function configure(ContainerConfigurator $configurator): void
8688
->class(ErrorProvider::class)
8789
->tag('api_platform.state_provider', ['key' => 'api_platform.state.error_provider']);
8890
}
89-
9091
}
9192

92-
9393
namespace App\Tests {
9494
use ApiPlatform\Playground\Test\TestGuideTrait;
9595
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
@@ -103,7 +103,7 @@ public function testBookDoesNotExists(): void
103103
static::createClient()->request('GET', '/books/1', options: ['headers' => ['accept' => 'application/ld+json']]);
104104
$this->assertResponseStatusCodeSame(400);
105105
$this->assertJsonContains([
106-
'detail' => 'les calculs ne sont pas bons'
106+
'detail' => 'les calculs ne sont pas bons',
107107
]);
108108
}
109109
}

docs/guides/error-resource.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// defaults:
1414
// rfc_7807_compliant_errors: true
1515
// ```
16+
1617
namespace App\ApiResource {
1718
use ApiPlatform\Metadata\ErrorResource;
1819
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
@@ -71,7 +72,6 @@ public static function provide(Operation $operation, array $uriVariables = [], a
7172
throw new MyDomainException('I am teapot');
7273
}
7374
}
74-
7575
}
7676

7777
namespace App\Tests {
@@ -90,7 +90,7 @@ public function testBookDoesNotExists(): void
9090
// you can override this by looking at the [Error Provider guide](/docs/guides/error-provider).
9191
$this->assertResponseStatusCodeSame(418);
9292
$this->assertJsonContains([
93-
'detail' => 'I am teapot'
93+
'detail' => 'I am teapot',
9494
]);
9595
}
9696
}
@@ -104,4 +104,3 @@ function request(): Request
104104
return Request::create('/books/1.jsonld', 'GET');
105105
}
106106
}
107-

docs/guides/provide-the-resource-state.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
3838
$book = new Book();
3939
$book->id = '1';
4040

41-
/** $book2 = new Book();
42-
$book2->id = '2'; */
41+
/* $book2 = new Book();
42+
* $book2->id = '2'; */
4343
// As an exercise you can edit the code and add a second book in the collection.
4444
return [$book/* $book2 */];
4545
}

docs/src/DependencyInjection/Compiler/AttributeFilterPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class AttributeFilterPass implements CompilerPassInterface
2727
{
2828
use AttributeFilterExtractorTrait;
2929

30-
private const TAG_FILTER_NAME = 'api_platform.filter';
30+
private const TAG_FILTER_NAME = 'api_platform.playground.filter';
3131

3232
/**
3333
* {@inheritdoc}

docs/src/DependencyInjection/Compiler/FilterPass.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ public function process(ContainerBuilder $container): void
3434
{
3535
$container
3636
->getDefinition('api_platform.filter_locator')
37-
->addArgument($this->findAndSortTaggedServices('api_platform.filter', $container));
37+
->addArgument($this->findAndSortTaggedServices('api_platform.playground.filter', $container));
3838
}
3939
}

0 commit comments

Comments
 (0)