"
}
@@ -255,19 +252,15 @@ use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
-/**
- * @ORM\Entity
- */
-#[ApiResource(iri: 'http://schema.org/Book')]
+#[ORM\Entity]
+#[ApiResource(iri: 'https://schema.org/Book')]
class Book
{
// ...
- /**
- * @ORM\ManyToOne(targetEntity=MediaObject::class)
- * @ORM\JoinColumn(nullable=true)
- */
- #[ApiProperty(iri: 'http://schema.org/image')]
+ #[ORM\ManyToOne(targetEntity: MediaObject::class)]
+ #[ORM\JoinColumn(nullable: true)]
+ #[ApiProperty(iri: 'https://schema.org/image')]
public ?MediaObject $image = null;
// ...
@@ -358,11 +351,11 @@ use Symfony\Component\Serializer\Annotation\Groups;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
- * @ORM\Entity
* @Vich\Uploadable
*/
+#[ORM\Entity]
#[ApiResource(
- iri: 'http://schema.org/Book',
+ iri: 'https://schema.org/Book',
normalizationContext: ['groups' => ['book:read']],
denormalizationContext: ['groups' => ['book:write']],
collectionOperations: [
@@ -378,7 +371,7 @@ class Book
{
// ...
- #[ApiProperty(iri: 'http://schema.org/contentUrl')]
+ #[ApiProperty(iri: 'https://schema.org/contentUrl')]
#[Groups(['book:read'])]
public ?string $contentUrl = null;
@@ -388,9 +381,7 @@ class Book
#[Groups(['book:write'])]
public ?File $file = null;
- /**
- * @ORM\Column(nullable=true)
- */
+ #[ORM\Column(nullable: true)]
public ?string $filePath = null;
// ...
diff --git a/core/filters.md b/core/filters.md
index 55c97098ec7..3f6d1397b4a 100644
--- a/core/filters.md
+++ b/core/filters.md
@@ -12,7 +12,7 @@ By default, all filters are disabled. They must be enabled explicitly.
When a filter is enabled, it automatically appears in the [OpenAPI](swagger.md) and [GraphQL](graphql.md) documentations.
It is also automatically documented as a `hydra:search` property for JSON-LD responses.
-
Watch the Filtering & Searching screencast
+

Watch the Filtering & Searching screencast
## Doctrine ORM and MongoDB ODM Filters
@@ -42,7 +42,7 @@ to a Resource in two ways:
We're linking the filter `offer.date_filter` with the resource like this:
- [codeSelector]
+
```php
```
- [/codeSelector]
+
2. By using the `#[ApiFilter]` attribute.
@@ -145,7 +145,7 @@ Syntax: `?property[]=foo&property[]=bar`
In the following example, we will see how to allow the filtering of a list of e-commerce offers:
-[codeSelector]
+
```php
`http://localhost:8000/api/offers?price=10` will return all offers with a price being exactly `10`.
`http://localhost:8000/api/offers?description=shirt` will return all offers with a description containing the word "shirt".
@@ -195,7 +195,7 @@ Filters can be combined together: `http://localhost:8000/api/offers?price=10&des
It is possible to filter on relations too, if `Offer` has a `Product` relation:
-[codeSelector]
+
```php
With this service definition, it is possible to find all offers belonging to the product identified by a given IRI.
Try the following: `http://localhost:8000/api/offers?product=/api/products/12`.
@@ -256,7 +256,7 @@ The `after` and `before` filters will filter including the value whereas `strict
Like others filters, the date filter must be explicitly enabled:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter offers by date with the following query: `/offers?createdAt[after]=2018-03-19`.
@@ -317,7 +317,7 @@ Always include items | `ApiPlatform\Core\Bridge\Doctrine\Orm\Fil
For instance, exclude entries with a property value of `null` with the following service definition:
-[codeSelector]
+
```php
### Boolean Filter
@@ -368,7 +368,7 @@ Syntax: `?property=`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter offers with the following query: `/offers?isAvailableGenericallyInMyCountry=true`.
@@ -423,7 +423,7 @@ Syntax: `?property=`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter offers with the following query: `/offers?sold=1`.
@@ -478,7 +478,7 @@ Syntax: `?property[]=value`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter the price with the following query: `/offers?price[between]=12.99..15.99`.
@@ -538,7 +538,7 @@ Previous syntax (deprecated): `?property[exists]=`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter offers on nullable field with the following query: `/offers?exists[transportFees]=true`.
@@ -605,7 +605,7 @@ Syntax: `?order[property]=`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/offers`, you can filter offers by name in ascending order and then by ID in descending
order with the following query: `/offers?order[name]=desc&order[id]=asc`.
@@ -656,7 +656,7 @@ order with the following query: `/offers?order[name]=desc&order[id]=asc`.
By default, whenever the query does not specify the direction explicitly (e.g.: `/offers?order[name]&order[id]`), filters
will not be applied unless you configure a default order direction to use:
-[codeSelector]
+
```php
#### Comparing with Null Values
@@ -712,7 +712,7 @@ Consider items as largest | `ApiPlatform\Core\Bridge\Doctrine\Orm\Fil
For instance, treat entries with a property value of `null` as the smallest, with the following service definition:
-[codeSelector]
+
```php
#### Using a Custom Order Query Parameter Name
@@ -772,7 +772,7 @@ api_platform:
Sometimes, you need to be able to perform filtering based on some linked resources (on the other side of a relation). All
built-in filters support nested properties using the dot (`.`) syntax, e.g.:
-[codeSelector]
+
```php
The above allows you to find offers by their respective product's color: `http://localhost:8000/api/offers?product.color=red`,
or order offers by the product's release date: `http://localhost:8000/api/offers?order[product.releaseDate]=desc`
@@ -835,7 +835,7 @@ As we have seen in previous examples, properties where filters can be applied mu
care about security and performance (e.g. an API with restricted access), it is also possible to enable built-in filters
for all properties:
-[codeSelector]
+
```php
**Note: Filters on nested properties must still be enabled explicitly, in order to keep things sane.**
@@ -902,7 +902,7 @@ Syntax: `?order[property]=`
Enable the filter:
-[codeSelector]
+
```php
Given that the collection endpoint is `/tweets`, you can filter tweets by id and date in ascending or descending order:
`/tweets?order[id]=asc&order[date]=desc`.
@@ -1215,10 +1215,12 @@ final class RegexpFilter extends AbstractContextAwareFilter
'property' => $property,
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
- 'swagger' => [
- 'description' => 'Filter using a regex. This will appear in the Swagger documentation!',
- 'name' => 'Custom name to use in the Swagger documentation',
- 'type' => 'Will appear below the name in the Swagger documentation',
+ 'description' => 'Filter using a regex. This will appear in the OpenApi documentation!',
+ 'openapi' => [
+ 'example' => 'Custom example that will be in the documentation and be the default value of the sandbox',
+ 'allowReserved' => false,// if true, query parameters will be not percent-encoded
+ 'allowEmptyValue' => true,
+ 'explode' => false, // to be true, the type must be Type::BUILTIN_TYPE_ARRAY, ?product=blue,green will be ?product=blue&product=green
],
];
}
@@ -1530,12 +1532,10 @@ use App\Entity\DummyCarColor;
#[ApiResource]
class DummyCar
{
- #[ORM\Id]
- #[ORM\GeneratedValue]
- #[ORM\Column(type: 'integer')]
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
- #[ORM\Column(type: 'string')]
+ #[ORM\Column]
#[ApiFilter(SearchFilter::class, strategy: 'partial')]
public ?string $name = null;
diff --git a/core/fosuser-bundle.md b/core/fosuser-bundle.md
index dca43bf913f..a518b84adb8 100644
--- a/core/fosuser-bundle.md
+++ b/core/fosuser-bundle.md
@@ -63,37 +63,29 @@ use FOS\UserBundle\Model\User as BaseUser;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Serializer\Annotation\Groups;
-/**
- * @ORM\Entity
- * @ORM\Table(name="fos_user")
- */
+#[ORM\Entity]
+#[ORM\Table(name: 'fos_user')]
#[ApiResource(
normalizationContext: ["groups" => ["user", "user:read"]],
denormalizationContext: ["groups" => ["user", "user:write"]]
)]
class User extends BaseUser
{
- /**
- * @ORM\Id
- * @ORM\Column(type="integer")
- * @ORM\GeneratedValue(strategy="AUTO")
- */
- protected $id;
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
+ protected ?int $id = null;
#[Groups("user")]
- protected $email;
+ protected string $email;
- /**
- * @ORM\Column(type="string", length=255, nullable=true)
- */
+ #[ORM\Column(nullable: true)]
#[Groups("user")]
- protected $fullname;
+ protected string $fullname;
#[Groups("user:write")]
- protected $plainPassword;
+ protected string $plainPassword;
#[Groups("user")]
- protected $username;
+ protected string $username;
public function setFullname(?string $fullname): void
{
diff --git a/core/getting-started.md b/core/getting-started.md
index a92863c4fc7..dc516e8afd1 100644
--- a/core/getting-started.md
+++ b/core/getting-started.md
@@ -47,24 +47,18 @@ use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
-/**
- * @ORM\Entity
- */
+#[ORM\Entity]
#[ApiResource]
class Product // The class name will be used to name exposed resources
{
- /**
- * @ORM\Id
- * @ORM\GeneratedValue(strategy="AUTO")
- * @ORM\Column(type="integer")
- */
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
/**
* A name property - this description will be available in the API documentation too.
*
- * @ORM\Column
*/
+ #[ORM\Column]
#[Assert\NotBlank]
public string $name = '';
@@ -72,8 +66,8 @@ class Product // The class name will be used to name exposed resources
/**
* @var Offer[]|ArrayCollection
*
- * @ORM\OneToMany(targetEntity="Offer", mappedBy="product", cascade={"persist"})
*/
+ #[ORM\OneToMany(targetEntity: Offer::class, mappedBy: 'product', cascade: ['persist'])]
public iterable $offers;
public function __construct()
@@ -117,32 +111,22 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* An offer from my shop - this description will be automatically extracted from the PHPDoc to document the API.
*
- * @ORM\Entity
*/
-#[ApiResource(iri: 'http://schema.org/Offer')]
+#[ORM\Entity]
+#[ApiResource(iri: 'https://schema.org/Offer')]
class Offer
{
- /**
- * @ORM\Column(type="integer")
- * @ORM\Id
- * @ORM\GeneratedValue(strategy="AUTO")
- */
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
- /**
- * @ORM\Column(type="text")
- */
+ #[ORM\Column(type: 'text')]
public string $description = '';
- /**
- * @ORM\Column(type="float")
- */
+ #[ORM\Column]
#[Assert\Range(minMessage: 'The price must be superior to 0.', min: 0)]
public float $price = -1.0;
- /**
- * @ORM\ManyToOne(targetEntity="Product", inversedBy="offers")
- */
+ #[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'offers')]
public ?Product $product = null;
public function getId(): ?int
@@ -182,7 +166,7 @@ It is also possible to override the naming convention using [operation path nami
As an alternative to annotations, you can map entity classes using YAML or XML:
-[codeSelector]
+
```yaml
# api/config/api_platform/resources.yaml
@@ -191,7 +175,7 @@ resources:
App\Entity\Offer:
shortName: 'Offer' # optional
description: 'An offer from my shop' # optional
- iri: 'http://schema.org/Offer' # optional
+ iri: 'https://schema.org/Offer' # optional
attributes: # optional
pagination_items_per_page: 25 # optional
```
@@ -209,12 +193,12 @@ resources:
class="App\Entity\Offer"
shortName="Offer"
description="An offer from my shop"
- iri="http://schema.org/Offer"
+ iri="https://schema.org/Offer"
/>
```
-[/codeSelector]
+
If you prefer to use YAML or XML files instead of annotations, you must configure API Platform to load the appropriate files:
diff --git a/core/graphql.md b/core/graphql.md
index 86522437301..1d93c98402b 100644
--- a/core/graphql.md
+++ b/core/graphql.md
@@ -382,7 +382,7 @@ For each resource, three mutations are available: one for creating it (`create`)
When updating or deleting a resource, you need to pass the **IRI** of the resource as argument. See [Global Object Identifier](#global-object-identifier) for more information.
-### Client Mutation Id
+### Client Mutation ID
Following the [Relay Input Object Mutations Specification](https://github.com/facebook/relay/blob/v7.1.0/website/spec/Mutations.md#relay-input-object-mutations-specification),
you can pass a `clientMutationId` as argument and can ask its value as a field.
@@ -503,7 +503,7 @@ Your custom mutations will be available like this:
}
mutation {
- withCustomArgsMutationBook(input: {sendMail: true, clientMutationId: "myId}) {
+ withCustomArgsMutationBook(input: {sendMail: true, clientMutationId: "myId"}) {
book {
title
}
@@ -584,7 +584,7 @@ You can also pass `clientSubscriptionId` as argument and can ask its value as a
In the payload of the subscription, the given fields of the resource will be the fields you subscribe to: if any of these fields is updated, you will be pushed their updated values.
-The `mercureUrl` field is the Mercure URL you need to use to [subscribe to the updates](https://mercure.rocks/docs/getting-started#subscribing) on the client side.
+The `mercureUrl` field is the Mercure URL you need to use to [subscribe to the updates](https://mercure.rocks/docs/getting-started#subscribing) on the client-side.
### Receiving an Update
@@ -1169,7 +1169,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
'normalization_context' => ['groups' => ['collection_query']],
'denormalization_context' => ['groups' => ['mutation']]
]
- }
+ ]
)]
class Book
{
@@ -1249,7 +1249,7 @@ final class ErrorHandler implements ErrorHandlerInterface
Then register the service:
-[codeSelector]
+
```yaml
# api/config/services.yaml
@@ -1291,7 +1291,7 @@ return function(ContainerConfigurator $configurator) {
};
```
-[/codeSelector]
+
### Formatting Exceptions and Errors
@@ -1451,9 +1451,7 @@ class Book
public $name;
- /**
- * @ORM\OneToMany(targetEntity="Book")
- */
+ #[ORM\OneToMany(targetEntity: Book::class)]
public $relatedBooks;
// ...
@@ -1646,7 +1644,7 @@ final class TypeConverter implements TypeConverterInterface
/**
* {@inheritdoc}
*/
- public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, string $resourceClass, string $rootResource, ?string $property, int $depth)
+ public function convertType(Type $type, bool $input, ?string $queryName, ?string $mutationName, ?string $subscriptionName, string $resourceClass, string $rootResource, ?string $property, int $depth)
{
if ('publicationDate' === $property
&& Book::class === $resourceClass
@@ -1654,7 +1652,7 @@ final class TypeConverter implements TypeConverterInterface
return 'DateTime';
}
- return $this->defaultTypeConverter->convertType($type, $input, $queryName, $mutationName, $resourceClass, $rootResource, $property, $depth);
+ return $this->defaultTypeConverter->convertType($type, $input, $queryName, $mutationName, $subscriptionName, $resourceClass, $rootResource, $property, $depth);
}
/**
@@ -1771,11 +1769,11 @@ use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
- * @ORM\Entity
* @Vich\Uploadable
*/
+#[ORM\Entity]
#[ApiResource(
- iri: 'http://schema.org/MediaObject',
+ iri: 'https://schema.org/MediaObject',
normalizationContext: [
'groups' => ['media_object_read']
],
@@ -1791,37 +1789,21 @@ use Vich\UploaderBundle\Mapping\Annotation as Vich;
)]
class MediaObject
{
- /**
- * @var int|null
- *
- * @ORM\Column(type="integer")
- * @ORM\GeneratedValue
- * @ORM\Id
- */
- protected $id;
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
+ protected ?int $id = null;
- /**
- * @var string|null
- *
- * @Groups({"media_object_read"})
- */
- #[ApiProperty(iri: 'http://schema.org/contentUrl')]
- public $contentUrl;
+ #[ApiProperty(iri: 'https://schema.org/contentUrl')]
+ #[Groups(['media_object_read'])]
+ public ?string $contentUrl = null;
/**
- * @var File|null
- *
- * @Assert\NotNull(groups={"media_object_create"})
* @Vich\UploadableField(mapping="media_object", fileNameProperty="filePath")
*/
- public $file;
+ #[Assert\NotNull(groups: ['media_object_create'])]
+ public ?File $file = null;
- /**
- * @var string|null
- *
- * @ORM\Column(nullable=true)
- */
- public $filePath;
+ #[ORM\Column(nullable: true)]
+ public ?string $filePath = null;
public function getId(): ?int
{
diff --git a/core/identifiers.md b/core/identifiers.md
index d697293cb43..27b8f7b52af 100644
--- a/core/identifiers.md
+++ b/core/identifiers.md
@@ -9,7 +9,7 @@ To help with your development experience, we introduced an identifier normalizat
Let's say you have the following class, which is identified by a `UUID` type. In this example, `UUID` is not a simple string but an object with many attributes.
-[codeSelector]
+
```php
```
-[/codeSelector]
+
Once registered as an `ApiResource`, having an existing person, it will be accessible through the following URL: `/people/110e8400-e29b-11d4-a716-446655440000`.
Note that the property identifying our resource is named `code`.
@@ -123,7 +123,7 @@ final class UuidNormalizer implements DenormalizerInterface
Tag this service as an `api_platform.identifier.denormalizer`:
-[codeSelector]
+
```yaml
services:
@@ -138,7 +138,7 @@ services:
```
-[/codeSelector]
+
Your `PersonDataProvider` will now work as expected!
@@ -155,26 +155,18 @@ use ApiPlatform\Core\Annotation\ApiProperty;
use App\Uuid;
use Doctrine\ORM\Mapping as ORM;
-/**
- * @ORM\Entity
- */
+#[ORM\Entity]
#[ApiResource]
final class Person
{
- /**
- * @var int
- *
- * @ORM\Id()
- * @ORM\GeneratedValue()
- * @ORM\Column(type="integer")
- */
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
#[ApiProperty(identifier: false)]
- private $id;
+ private ?int $id = null;
/**
* @var Uuid
- * @ORM\Column(type="uuid", unique=true)
*/
+ #[ORM\Column(type: 'uuid', unique: true)]
#[ApiProperty(identifier: true)]
public $code;
diff --git a/core/json-schema.md b/core/json-schema.md
index 13bb81888d7..9519709f971 100644
--- a/core/json-schema.md
+++ b/core/json-schema.md
@@ -49,9 +49,7 @@ use Doctrine\ORM\Mapping as ORM;
#[ApiResource]
class Greeting
{
- #[ORM\Id]
- #[ORM\GeneratedValue]
- #[ORM\Column(type: "integer")]
+ #[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
// [...]
diff --git a/core/jwt.md b/core/jwt.md
index d3d6d954d7b..2ebeb678f3a 100644
--- a/core/jwt.md
+++ b/core/jwt.md
@@ -42,7 +42,7 @@ The keys should not be checked in to the repository (i.e. it's in `api/.gitignor
only pass signature validation against the same pair of keys it was signed with. This is especially relevant in a production
environment, where you don't want to accidentally invalidate all your clients' tokens at every deployment.
-For more information, refer to [the bundle's documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md)
+For more information, refer to [the bundle's documentation](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst)
or read a [general introduction to JWT here](https://jwt.io/introduction/).
We're not done yet! Let's move on to configuring the Symfony SecurityBundle for JWT authentication.
@@ -105,7 +105,7 @@ authentication_token:
```
If you want to avoid loading the `User` entity from database each time a JWT token needs to be authenticated, you may consider using
-the [database-less user provider](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/8-jwt-user-provider.md) provided by LexikJWTAuthenticationBundle. However, it means you will have to fetch the `User` entity from the database yourself as needed (probably through the Doctrine EntityManager).
+the [database-less user provider](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/8-jwt-user-provider.rst) provided by LexikJWTAuthenticationBundle. However, it means you will have to fetch the `User` entity from the database yourself as needed (probably through the Doctrine EntityManager).
Refer to the section on [Security](security.md) to learn how to control access to API resources and operations. You may
also want to [configure Swagger UI for JWT authentication](#documenting-the-authentication-mechanism-with-swaggeropen-api).
@@ -120,7 +120,7 @@ security:
# https://symfony.com/doc/current/security.html#c-hashing-passwords
password_hashers:
App\Entity\User: 'auto'
-
+
# https://symfony.com/doc/current/security/authenticator_manager.html
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
@@ -154,6 +154,18 @@ security:
- { path: ^/, roles: IS_AUTHENTICATED_FULLY }
```
+### Be sure to have lexik_jwt_authentication configured on your user_identity_field
+
+```yaml
+# api/config/packages/lexik_jwt_authentication.yaml
+lexik_jwt_authentication:
+ secret_key: '%env(resolve:JWT_SECRET_KEY)%'
+ public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
+ pass_phrase: '%env(JWT_PASSPHRASE)%'
+
+ user_identity_field: email # Or the field you have setted using make:user
+```
+
## Documenting the Authentication Mechanism with Swagger/Open API
Want to test the routes of your JWT-authentication-protected API?
@@ -165,7 +177,7 @@ Want to test the routes of your JWT-authentication-protected API?
api_platform:
swagger:
api_keys:
- apiKey:
+ JWT:
name: Authorization
type: header
```
@@ -177,8 +189,8 @@ The "Authorize" button will automatically appear in Swagger UI.
### Adding a New API Key
All you have to do is configure the API key in the `value` field.
-By default, [only the authorization header mode is enabled](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#2-use-the-token) in LexikJWTAuthenticationBundle.
-You must set the [JWT token](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/master/Resources/doc/index.md#1-obtain-the-token) as below and click on the "Authorize" button.
+By default, [only the authorization header mode is enabled](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst#2-use-the-token) in LexikJWTAuthenticationBundle.
+You must set the [JWT token](https://github.com/lexik/LexikJWTAuthenticationBundle/blob/2.x/Resources/doc/index.rst#1-obtain-the-token) as below and click on the "Authorize" button.
`Bearer MY_NEW_TOKEN`
@@ -238,6 +250,13 @@ final class JwtDecorator implements OpenApiFactoryInterface
],
]);
+ $schemas = $openApi->getComponents()->getSecuritySchemes() ?? [];
+ $schemas['JWT'] = new \ArrayObject([
+ 'type' => 'http',
+ 'scheme' => 'bearer',
+ 'bearerFormat' => 'JWT',
+ ]);
+
$pathItem = new Model\PathItem(
ref: 'JWT Token',
post: new Model\Operation(
@@ -266,6 +285,7 @@ final class JwtDecorator implements OpenApiFactoryInterface
],
]),
),
+ security: [],
),
);
$openApi->getPaths()->addPath('/authentication_token', $pathItem);
@@ -280,11 +300,11 @@ And register this service in `config/services.yaml`:
```yaml
# api/config/services.yaml
services:
- # ...
+ # ...
App\OpenApi\JwtDecorator:
decorates: 'api_platform.openapi.factory'
- arguments: ['@.inner']
+ arguments: ['@.inner']
```
## Testing
@@ -308,14 +328,15 @@ class AuthenticationTest extends ApiTestCase
public function testLogin(): void
{
$client = self::createClient();
+ $container = self::getContainer();
$user = new User();
$user->setEmail('test@example.com');
$user->setPassword(
- self::$container->get('security.user_password_hasher')->hashPassword($user, '$3CR3T')
+ $container->get('security.user_password_hasher')->hashPassword($user, '$3CR3T')
);
- $manager = self::$container->get('doctrine')->getManager();
+ $manager = $container->get('doctrine')->getManager();
$manager->persist($user);
$manager->flush();
@@ -344,3 +365,26 @@ class AuthenticationTest extends ApiTestCase
```
Refer to [Testing the API](../distribution/testing.md) for more information about testing API Platform.
+
+### Improving Tests Suite Speed
+
+Since now we have a `JWT` authentication, functional tests require us to log in each time we want to test an API endpoint. This is where [Password Hashers](https://symfony.com/doc/current/security/passwords.html) come into play.
+
+Hashers are used for 2 reasons:
+
+1. To generate a hash for a raw password (`$container->get('security.user_password_hasher')->hashPassword($user, '$3CR3T')`)
+2. To verify a password during authentication
+
+While hashing and verifying 1 password is quite a fast operation, doing it hundreds or even thousands of times in a tests suite becomes a bottleneck, because reliable hashing algorithms are slow by their nature.
+
+To significantly improve the test suite speed, we can use more simple password hasher specifically for the `test` environment.
+
+```yaml
+# override in api/config/packages/test/security.yaml for test env
+security:
+ password_hashers:
+ App\Entity\User:
+ algorithm: md5
+ encode_as_base64: false
+ iterations: 0
+```
diff --git a/core/mercure.md b/core/mercure.md
index 3cda83f2927..1238a64c5eb 100644
--- a/core/mercure.md
+++ b/core/mercure.md
@@ -4,7 +4,7 @@ API Platform can automatically push the modified version of the resources expose
> *Mercure* is a protocol allowing to push data updates to web browsers and other HTTP clients in a convenient, fast, reliable and battery-efficient way. It is especially useful to publish real-time updates of resources served through web APIs, to reactive web and mobile apps.
>
-> —
+> —[https://mercure.rocks](https://mercure.rocks)
API Platform detects changes made to your Doctrine entities, and sends the updated resources to the Mercure hub.
Then, the Mercure hub dispatches the updates to all connected clients using [Server-sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events).
diff --git a/core/messenger.md b/core/messenger.md
index 6c9e475ff5d..36460835882 100644
--- a/core/messenger.md
+++ b/core/messenger.md
@@ -20,7 +20,7 @@ docker-compose exec php \
Set the `messenger` attribute to `true`, and API Platform will automatically dispatch the API Resource instance as a message using the message bus provided by the Messenger Component. The following example allows you to create a new `Person` in an asynchronous manner:
-[codeSelector]
+
```php
Because the `messenger` attribute is `true`, when a `POST` is handled by API Platform, the corresponding instance of the `Person` will be dispatched.
diff --git a/core/mongodb.md b/core/mongodb.md
index 56c11d8da90..f03115499ad 100644
--- a/core/mongodb.md
+++ b/core/mongodb.md
@@ -170,7 +170,7 @@ use Symfony\Component\Validator\Constraints as Assert;
/**
* @ODM\Document
*/
-#[ApiResource(iri: "http://schema.org/Offer")]
+#[ApiResource(iri: "https://schema.org/Offer")]
class Offer
{
/**
@@ -203,7 +203,7 @@ class Offer
}
```
-When defining references, always use the id for storing them instead of the native [DBRef](https://docs.mongodb.com/manual/reference/database-references/#dbrefs).
+When defining references, always use the ID for storing them instead of the native [DBRef](https://docs.mongodb.com/manual/reference/database-references/#dbrefs).
It allows API Platform to manage [filtering on nested properties](filters.md#apifilter-annotation) by using [lookups](https://docs.mongodb.com/manual/reference/operator/aggregation/lookup/).
## Filtering
diff --git a/core/openapi.md b/core/openapi.md
index 005c3e054a3..75d98c58d29 100644
--- a/core/openapi.md
+++ b/core/openapi.md
@@ -109,7 +109,7 @@ The impact on the swagger-ui is the following:
Sometimes you may want to change the information included in your OpenAPI documentation.
The following configuration will give you total control over your OpenAPI definitions:
-[codeSelector]
+
```php
"string", "format" => "date-time"]
@@ -209,7 +201,7 @@ resources:
```
-[/codeSelector]
+
This will produce the following Swagger documentation:
@@ -301,7 +293,7 @@ class User
You also have full control over both built-in and custom operations documentation.
-[codeSelector]
+
```php
```
-[/codeSelector]
+

@@ -444,7 +436,9 @@ Change `/docs` to the URI you wish Swagger to be accessible on.
## Using a custom Asset Package in Swagger UI
-Sometimes you may want to use a different [Asset Package](https://symfony.com/doc/current/reference/configuration/framework.html#packages) for the Swagger UI. In this way you'll have more fine-grained control over the asset url generations. This is useful i.e. if you want to use different base path, base url or asset versioning strategy.
+Sometimes you may want to use a different [Asset Package](https://symfony.com/doc/current/reference/configuration/framework.html#packages) for the Swagger UI.
+In this way you'll have more fine-grained control over the asset URL generations.
+This is useful i.e. if you want to use different base path, base URL or asset versioning strategy.
Specify a custom asset package name:
@@ -482,7 +476,7 @@ As described [in the Symfony documentation](https://symfony.com/doc/current/temp