Skip to content

Commit

Permalink
ci: guides documentation with doctrine search parameters (#6328)
Browse files Browse the repository at this point in the history
* docs: how to run a local guide

* docs: document parameter with doctrine filter
  • Loading branch information
soyuka committed Apr 21, 2024
1 parent feea1b0 commit 6d15e22
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 38 deletions.
16 changes: 16 additions & 0 deletions docs/README.md
Expand Up @@ -4,6 +4,8 @@

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.

Read the "[How To Guide](./guides/how-to.php)" to understand how to write an API Platform guide.

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.

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

A build of [php-wasm](https://github.com/soyuka/php-wasm) is needed in the `public/php-wasm` directory to try it out.

## Local tests

First run `composer update`.

Then, get the [`pdg-phpunit`](https://github.com/php-documentation-generator/php-documentation-generator/tags) binary that allows to run single-file test.

Use `KERNEL_CLASS` and `PDG_AUTOLOAD` to run a guide:

```
APP_DEBUG=0 \
PDG_AUTOLOAD='vendor/autoload.php' \
KERNEL_CLASS='\ApiPlatform\Playground\Kernel' pdg-phpunit guides/doctrine-search-filter.php
```
2 changes: 1 addition & 1 deletion docs/guides/create-a-custom-doctrine-filter.php
Expand Up @@ -33,7 +33,7 @@ final class RegexpFilter extends AbstractFilter
/*
* Filtered properties is accessible through getProperties() method: property => strategy
*/
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, Operation $operation = null, array $context = []): void
protected function filterProperty(string $property, $value, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void
{
/*
* Otherwise this filter is applied to order and page as well.
Expand Down
59 changes: 32 additions & 27 deletions docs/guides/doctrine-search-filter.php
Expand Up @@ -12,18 +12,18 @@
// By default, all filters are disabled. They must be enabled explicitly.

namespace App\Entity {
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use Doctrine\ORM\Mapping as ORM;

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

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

namespace App\DependencyInjection {
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

function configure(ContainerConfigurator $configurator): void
{
// 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.
$services = $configurator->services();
$services->set('app.search_filter')
->parent('api_platform.doctrine.orm.search_filter')
// Search strategies may be defined here per properties, [read more](https://api-platform.com/docs/core/filters/) on the filter documentation.
->args([['author' => 'partial', 'title' => 'partial']])
->tag('api_platform.filter');
}
}

namespace App\Playground {
use Symfony\Component\HttpFoundation\Request;

Expand Down Expand Up @@ -85,8 +97,7 @@ public function load(ObjectManager $manager): void
$bookFactory->many(10)->create(fn () => [
'title' => faker()->name(),
'author' => faker()->firstName(),
]
);
]);
}
}
}
Expand All @@ -100,36 +111,30 @@ final class BookTest extends ApiTestCase
{
use TestGuideTrait;

public function testAsAnonymousICanAccessTheDocumentation(): void
public function testGetDocumentation(): void
{
static::createClient()->request('GET', '/books.jsonld');

$this->assertResponseIsSuccessful();
$this->assertMatchesResourceCollectionJsonSchema(Book::class, '_api_/books{._format}_get_collection', 'jsonld');
$this->assertMatchesResourceCollectionJsonSchema(Book::class, '_api_books{._format}_get_collection', 'jsonld');
$this->assertJsonContains([
'hydra:search' => [
'@type' => 'hydra:IriTemplate',
'hydra:template' => '/books.jsonld{?title,title[],author}',
'hydra:template' => '/books.jsonld{?author,title}',
'hydra:variableRepresentation' => 'BasicRepresentation',
'hydra:mapping' => [
[
'@type' => 'IriTemplateMapping',
'variable' => 'title',
'property' => 'title',
'variable' => 'author',
'property' => 'author',
'required' => false,
],
[
'@type' => 'IriTemplateMapping',
'variable' => 'title[]',
'variable' => 'title',
'property' => 'title',
'required' => false,
],
[
'@type' => 'IriTemplateMapping',
'variable' => 'author',
'property' => 'author',
'required' => false,
],
],
],
]);
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/error-provider.php
Expand Up @@ -14,6 +14,7 @@
// rfc_7807_compliant_errors: true
// ```
// To customize the API Platform response, replace the api_platform.state.error_provider with your own provider:

namespace App\ApiResource {
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
Expand Down Expand Up @@ -75,6 +76,7 @@ public function provide(Operation $operation, array $uriVariables = [], array $c

// This is replacing the service, the "key" is important as this is the provider we
// will look for when handling an exception.

namespace App\DependencyInjection {
use App\State\ErrorProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
Expand All @@ -86,10 +88,8 @@ function configure(ContainerConfigurator $configurator): void
->class(ErrorProvider::class)
->tag('api_platform.state_provider', ['key' => 'api_platform.state.error_provider']);
}

}


namespace App\Tests {
use ApiPlatform\Playground\Test\TestGuideTrait;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
Expand All @@ -103,7 +103,7 @@ public function testBookDoesNotExists(): void
static::createClient()->request('GET', '/books/1', options: ['headers' => ['accept' => 'application/ld+json']]);
$this->assertResponseStatusCodeSame(400);
$this->assertJsonContains([
'detail' => 'les calculs ne sont pas bons'
'detail' => 'les calculs ne sont pas bons',
]);
}
}
Expand Down
5 changes: 2 additions & 3 deletions docs/guides/error-resource.php
Expand Up @@ -13,6 +13,7 @@
// defaults:
// rfc_7807_compliant_errors: true
// ```

namespace App\ApiResource {
use ApiPlatform\Metadata\ErrorResource;
use ApiPlatform\Metadata\Exception\ProblemExceptionInterface;
Expand Down Expand Up @@ -71,7 +72,6 @@ public static function provide(Operation $operation, array $uriVariables = [], a
throw new MyDomainException('I am teapot');
}
}

}

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

4 changes: 2 additions & 2 deletions docs/guides/provide-the-resource-state.php
Expand Up @@ -38,8 +38,8 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
$book = new Book();
$book->id = '1';

/** $book2 = new Book();
$book2->id = '2'; */
/* $book2 = new Book();
* $book2->id = '2'; */
// As an exercise you can edit the code and add a second book in the collection.
return [$book/* $book2 */];
}
Expand Down
Expand Up @@ -27,7 +27,7 @@ final class AttributeFilterPass implements CompilerPassInterface
{
use AttributeFilterExtractorTrait;

private const TAG_FILTER_NAME = 'api_platform.filter';
private const TAG_FILTER_NAME = 'api_platform.playground.filter';

/**
* {@inheritdoc}
Expand Down
2 changes: 1 addition & 1 deletion docs/src/DependencyInjection/Compiler/FilterPass.php
Expand Up @@ -34,6 +34,6 @@ public function process(ContainerBuilder $container): void
{
$container
->getDefinition('api_platform.filter_locator')
->addArgument($this->findAndSortTaggedServices('api_platform.filter', $container));
->addArgument($this->findAndSortTaggedServices('api_platform.playground.filter', $container));
}
}

0 comments on commit 6d15e22

Please sign in to comment.