ccmbenchmark / ting_bundle
Symfony bundle for ccmbenchmark/ting. Provides an interface to use a lightweight datamapper in symfony.
Package info
github.com/ccmbenchmark/ting_bundle
Type:symfony-bundle
pkg:composer/ccmbenchmark/ting_bundle
Requires
- php: >=8.1
- ccmbenchmark/ting: ^3.13
- doctrine/cache: ^1.10
- symfony/config: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/dependency-injection: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/http-kernel: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/service-contracts: ^1.0 || ^2.0 || ^3.0
- symfony/stopwatch: ^4.4 || ^5.0 || ^6.0 || ^7.0
- symfony/validator: ^4.4 || ^5.0 || ^6.0 || ^7.0
Requires (Dev)
- atoum/atoum: ^4.2
- atoum/stubs: ^2.2
- brick/geo: >=0.5 <=1.0
- symfony/expression-language: ^6.3 || ^7.2
- symfony/security-bundle: ^6.0 || ^7.0
- symfony/security-core: ^6.0 || ^7.0
- symfony/serializer: ^6.0 || ^7.0
- symfony/uid: ^6.0 || ^7.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-master
- 3.11.0
- 3.10.1
- 3.10.0
- 3.9.0
- 3.8.2
- 3.8.1
- 3.8.0
- 3.7.0
- 3.6.1
- 3.6.0
- 3.5.0
- 3.4.2
- 3.4.1
- 3.4.0
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.3
- 3.2.2
- 3.2.1
- 3.2.0
- 3.1.0
- 3.0.1
- 3.0.0
- 2.2.0
- 2.1.0
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.2.0
- 1.1.x-dev
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.0
- dev-feature/compatibility-ting-v4
- dev-feature/hydratable-vo
- dev-feature/support-sf7
- dev-fix/fix-deprecated
- dev-release/3.6.1
- dev-release/3.6.0
- dev-fix/dynamic-property
- dev-feature/repository-configuration
- dev-feature/cache-clearer
- dev-ci/php8
- dev-fix/symfony5
- dev-feature/implement-LateDataCollectorInterface
- dev-feature/tests-workflows
- dev-fix-srm-library-type
- dev-feature/jv/test-ting-clean-1
This package is auto-updated.
Last update: 2026-09-28 10:42:15 UTC
README
⚠️ This repository is no longer actively maintained. The project is now maintained on the internal GitLab of CCM Benchmark, and new versions (4.0.0 and later) are published there only. Pull requests are still welcome here: interesting ones will be ported.
Installation
- Require Ting Bundle with
composer require ccmbenchmark/ting_bundle - Load Bundles in AppKernel.php
new CCMBenchmark\TingBundle\TingBundle(),
Table of contents
- Configuration
- Using Ting as a User Provider
- Declare a unique constraint
- Using Ting as a Value Resolver
Configuration
Main configuration
#!yaml
ting:
repositories: # Unnecessary if entities registered with attributes
Acme:
namespace: Acme\DemoBundle\Entity
directory: "@DemoBundle/Entity"
options:
#pass options to your repository
Acme\DemoBundle\BazRepository:
extra:
bar: hello
foo: world
Acme\DemoBundle\FooRepository:
extra:
bar: hello
foo: world
default:
connection: main
database: baz
connections:
main:
namespace: CCMBenchmark\Ting\Driver\Mysqli
master:
host: localhost
user: world_sample
password: world_sample
port: 3306
slaves:
slave1:
host: 127.0.0.1
user: world_sample_ro
password: world_sample_ro
port: 3306
slave2:
host: 127.0.1.1
user: world_sample_ro
password: world_sample_ro
port: 3306
databases_options:
baz:
timezone: 'Europe/Paris'
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 namespace App\Entity; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; class City implements NotifyPropertyInterface { use NotifyProperty; public string $name; public function setName(string $name): void { $this->propertyChanged('name', $this->name ?? null, $name); $this->name = $name; } }
For PHP >= 8.4, you may use a property hook instead. This hook will be bypassed by Ting for hydratation.
<?php namespace App\Entity; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; class City implements NotifyPropertyInterface { use NotifyProperty; public string $name { set(string $name) { $this->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
// src/Entity/City.php <?php namespace App\Entity; namespace tests\fixtures; use App\Repository\CityRepository; use Brick\Geo\Point; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; use CCMBenchmark\TingBundle\Schema; use Symfony\Component\Uid\Uuid; use Symfony\Component\Uid\UuidV4; #[Schema\Table('city_table', 'connectionName', '%env(DATABASE_NAME)%', CityRepository::class)] class City implements NotifyPropertyInterface { use NotifyProperty; #[Schema\Column(autoIncrement: true, primary: true)] public int $id { set(int $id) { $this->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; } }; }
// src/Repository/CityRepository.php <?php namespace App\Repository; class CityRepository extends CCMBenchmark\Ting\Repository\Repository { }
Using Ting as a User Provider
User providers (re)load users from a storage based on a "user identifier" (extract from symfony documentation).
Ting can be used as a User Provider, it's automatically registered by the bundle as the provider ting. To do so, update your security configuration.
security: # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: app_user_provider: ting: class: App\Entity\User property: email
Your entity will have to implements the following interfaces: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface (for password authenticated users) and Symfony\Component\Security\Core\User\UserInterface (common to all kind of users).
It needs to implement __serialize too.
Declare a Unique constraint in a table
If you use the component symfony/validator, you may need to ensure that a value (or a combination of them) is unique in your table.
You can use the Constraint CCMBenchmark\TingBundle\Validator\Constraints\UniqueEntity to do so. In can be used as an annotation, or as an attribute.
Example:
namespace App\Entity; use App\Repository\UserRepository; use CCMBenchmark\Ting\Entity\NotifyProperty; use CCMBenchmark\Ting\Entity\NotifyPropertyInterface; use CCMBenchmark\TingBundle\Schema\Column; use CCMBenchmark\TingBundle\Schema\Table; use CCMBenchmark\TingBundle\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Serializer\Attribute\Groups; use Symfony\Component\Validator\Constraints as Assert; #[Table(name: 'users', connection: 'main', database: '%env(DATABASE_DB_NAME)%', repository: UserRepository::class)] #[UniqueEntity(options:['repository' => 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 namespace App\Controller; use App\Entity\User; use App\Repository\UserRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Validator\Validator\ValidatorInterface; #[Route('/users', methods:['POST'], format: 'json')] class createUserController extends AbstractController { public function __construct (private readonly ValidatorInterface $validator, private readonly UserRepository $userRepository) { } public function __invoke( #[MapRequestPayload(serializationContext: ['groups' => ['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.
You can automatically map request parameters to entities:
- 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) - Map it to your action parameters:
- Add it to your signature:
public function getUser(User $user) - Update the route to do the mapping:
/api/users/{userId:user}(in this case: theUserhavinguserIdmatching the request will be fetched and injected to your action with the argument$user)
- Add it to your signature:
<?php namespace App\Controller; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Security\Http\Attribute\IsGranted; class UserController { #[Route('/api/users/{userId:myUser}', name: 'get_user', methods: ['GET'], format: 'json' )] #[IsGranted('ROLE_USER')] public function getUser(User $user): JsonResponse { return new JsonResponse($this->serializer->serialize($user, 'json', ['groups' => 'default']), json: true); } }
For more advanced use cases, you can leverage:
- The Expression Language component
- The
CCMBenchmark\TingBundle\Attribute\MapEntityattribute
Example:
<?php namespace App\Controller; use CCMBenchmark\TingBundle\Attribute\MapEntity; use Symfony\Component\Security\Http\Attribute\IsGranted; class UserController { #[Route('/api/users/{firstname}/{lastname}', name: 'get_user', methods: ['GET'], format: 'json' )] #[IsGranted('ROLE_USER')] public function getUser(#[MapEntity(expr: 'repository.getOneBy({"firstname": firstname, "lastname": lastname}')] User $user): JsonResponse { return new JsonResponse($this->serializer->serialize($user, 'json', ['groups' => 'default']), json: true); } }