// src/App/Entity.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use App\Validator\Constraints\MinimalProperties;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* A product.
*/
#[ORM\Entity]
#[ApiResource]
class Product
{
#[ORM\Id, ORM\Column, ORM\GeneratedValue]
private ?int $id = null;
#[ORM\Column]
#[Assert\NotBlank]
public string $name;
/**
* @var string[] Describe the product
*/
#[MinimalProperties]
#[ORM\Column(type: 'json')]
public $properties;
public function getId(): ?int
{
return $this->id;
}
}
// src/App/Validator/Constraints.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
class MinimalProperties extends Constraint
{
public $message = 'The product must have the minimal properties required ("description", "price")';
}
// src/App/Validator/Constraints.php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
final class MinimalPropertiesValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint): void
{
if (!\array_key_exists('description', $value) || !\array_key_exists('price', $value)) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
// src/DoctrineMigrations.php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Migration extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE product (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, properties CLOB NOT NULL)');
}
}
// src/App/Playground.php
namespace App\Playground;
use Symfony\Component\HttpFoundation\Request;
function request(): Request
{
return Request::create(
uri: '/products',
method: 'POST',
server: [
'CONTENT_TYPE' => 'application/ld+json',
'HTTP_ACCEPT' => 'application/ld+json',
],
content: '{"name": "test", "properties": {"description": "Test product"}}'
);
}
// src/App/Tests.php
namespace App\Tests;
use ApiPlatform\Playground\Test\TestGuideTrait;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
final class BookTest extends ApiTestCase
{
use TestGuideTrait;
public function testValidation(): void
{
$response = static::createClient()->request(method: 'POST', url: '/products', options: [
'json' => ['name' => 'test', 'properties' => ['description' => 'foo']],
'headers' => ['content-type' => 'application/ld+json'],
]);
If the data submitted by the client is invalid, the HTTP status code will be set to 422 Unprocessable Entity and the response’s body will contain the list of violations serialized in a format compliant with the requested one. For instance, a validation error will look like the following if the requested format is JSON-LD (the default):
{
"@context": "/contexts/ConstraintViolationList",
"@type": "ConstraintViolationList",
"title": "An error occurred",
"description": "properties: The product must have the minimal properties required (\"description\", \"price\")",
"violations": [
{
"propertyPath": "properties",
"message": "The product must have the minimal properties required (\"description\", \"price\")"
}
]
}
$this->assertResponseStatusCodeSame(422);
$this->assertJsonContains([
'description' => 'properties: The product must have the minimal properties required ("description", "price")',
'title' => 'An error occurred',
'violations' => [
['propertyPath' => 'properties', 'message' => 'The product must have the minimal properties required ("description", "price")'],
],
]);
}
}
Made with love by
Les-Tilleuls.coop can help you design and develop your APIs and web projects, and train your teams in API Platform, Symfony, Next.js, Kubernetes and a wide range of other technologies.
Learn more