diff --git a/README.md b/README.md index 9d1f58f..05cba77 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,24 @@ Installation new CCMBenchmark\TingBundle\TingBundle(), ``` +## Table of contents +- [Configuration](#configuration) + - [Main configuration](#main-configuration) + - [About public properties](#about-public-properties) + - [Declare metadata with attributes](#declare-metadata-with-attributes) +- [Using Ting as a User Provider](#using-ting-a-user-provider) +- [Declare a unique constraint](#declare-a-unique-constraint-in-a-table) +- [Using Ting as a Value Resolver](#using-ting-as-a-value-resolver) + Configuration ============= + +## Main configuration ``` #!yaml ting: - repositories: + repositories: # Unnecessary if entities registered with attributes Acme: namespace: Acme\DemoBundle\Entity directory: "@DemoBundle/Entity" @@ -58,11 +69,278 @@ Configuration timezone: 'Europe/Paris' ``` -Usage -=========== -From your controller call +## About public properties +Public properties can be used in your entities, however for PHP < 8.4, you should declare a setter to notify the property change. + +PHP < 8.4: + +```php +propertyChanged('name', $this->name ?? null, $name); + $this->name = $name; + } + +} + ``` -#!php - $this->get('ting')->get('\Acme\DemoBundle\Entity\AcmeRepository'); +For PHP >= 8.4, you may use a property hook instead. This hook will be bypassed by Ting for hydratation. + +```php +propertyChanged('name', $this->name ?? null, $name); + $this->name = $name; + } + }; +} ``` + +### A note about uninitialized typed properties +- When persisting an entity with uninitialized typed property, the property will be ignored ; a default value must be defined in your database for this column to prevent a failure +- You cannot access an uninitialized typed property, PHP will trigger an error + +## Declare metadata with attributes +Attributes are provided to declare an entity. Relevant attributes are available in `CCMBenchmark\TingBundle\Schema`. + +### Table +- Full name: `CCMBenchmark\TingBundle\Schema\Table` +- This attribute must be added to your class, with all relevant options (table, connection, etc.). + +### Column +- Full name: `CCMBenchmark\TingBundle\Schema\Column` +- This attribute must be added to every property mapped to the database. Serialization is inferred from the type, if available. + +### Full example + +```php +// src/Entity/City.php +propertyChanged('id', $this->id ?? null, $id); + $this->id = $id; + } + }; + + #[Schema\Column(column: 'field')] + public string $fieldWithSpecifiedColumnName { + set (string $fieldWithSpecifiedColumnName) { + $this->propertyChanged('fieldWithSpecifiedColumnName', $this->fieldWithSpecifiedColumnName ?? null, $fieldWithSpecifiedColumnName); + $this->fieldWithSpecifiedColumnName = $fieldWithSpecifiedColumnName; + } + }; +} +``` +```php +// src/Repository/CityRepository.php + UserRepository::class, 'fields' => ['email']], groups: ['create'])] +class User implements UserInterface, NotifyPropertyInterface { + #[Column(autoIncrement: true, primary: true)] + public int $id { set(int $id) { + $this->propertyChanged('id', $this->id ?? null, $id); + $this->id = $id; + }} + + #[Column] + #[Groups(['default', 'create', 'update', 'service_account'])] + #[Assert\NotBlank(groups: ["default", "create", "update"])] + #[Assert\Email(groups: ["default", "create", "update"])] + public string $email { set(string $email) { + $this->propertyChanged('email', $this->email ?? null, $email); + $this->email = $email; + } } + + public function getRoles(): array + { + return ['ROLE_USER']; + } + + public function eraseCredentials(): void + { + + } + + public function getUserIdentifier(): string + { + return $this->email; + } + + public function __serialize(): array + { + return [ + 'id' => $this->id, + 'email' => $this->email, + ]; + } +} +``` + +With that example you can assert, when creating a new user, that the email address is unique: + +```php + ['create']])] User $user + ) :JsonResponse { + $violations = $this->validator->validate($user, groups: ['create']); + if ($violations->count() > 0) { + return new JsonResponse(['message' => 'Errors...'], 422); + } + $this->userRepository->save($user); + return new JsonResponse(['message' => 'User registered'], 201); + } +} + +``` + +## Using Ting as a Value Resolver +This bundle automatically registers a [Value Resolver](https://symfony.com/doc/current/controller/value_resolver.html#built-in-value-resolvers). + +You can automatically map request parameters to entities: +1. Declare a parameter in your route (i.e: `/api/users/{userId}`), using the property in your entity you'll use to fetch data (in this case: `userId`) +2. Map it to your action parameters: + 1. Add it to your signature: `public function getUser(User $user)` + 2. Update the route to do the mapping: `/api/users/{userId:user}` (in this case: the `User` having `userId` matching the request will be fetched and injected to your action with the argument `$user`) + +```php +serializer->serialize($user, 'json', ['groups' => 'default']), json: true); + } +} +``` + +For more advanced use cases, you can leverage: +- [The Expression Language component](https://symfony.com/doc/current/components/expression_language.html) +- The `CCMBenchmark\TingBundle\Attribute\MapEntity` attribute + +Example: + +```php +serializer->serialize($user, 'json', ['groups' => 'default']), json: true); + } +} +``` \ No newline at end of file