Cursor Rules : Révolutionnez la Qualité de Votre Code Symfony et API Platform
Dans l'écosystème du développement moderne, l'Intelligence Artificielle est devenue un partenaire incontournable. Mais avez-vous déjà ressenti cette frustration quand votre IA génère du code qui ne respecte pas vos conventions de projet ? C'est exactement le problème que résolvent les Cursor Rules - un système révolutionnaire qui transforme votre IA en expert de vos standards de développement.
🎯 Qu'est-ce que les Cursor Rules ?
Le concept fondamental
Les Cursor Rules sont un système de directives qui permettent de personnaliser le comportement de l'IA intégrée à l'éditeur Cursor. Plutôt que de laisser l'IA générer du code générique, ces règles lui fournissent un contexte précis sur :
- ✅ Vos conventions de codage
- ✅ Votre architecture de projet
- ✅ Vos standards de qualité
- ✅ Vos préférences techniques
Évolution : De .cursorrules
aux Project Rules
Historiquement, ces directives étaient stockées dans un fichier .cursorrules
à la racine du projet. Cependant, Cursor a évolué vers un système plus sophistiqué : les Project Rules, stockées dans le répertoire .cursor/rules/
pour une meilleure flexibilité et un contrôle granulaire.
votre-projet/
├── .cursor/
│ └── rules/
│ ├── symfony.md
│ ├── api-platform.md
│ └── custom-standards.md
├── src/
└── composer.json
🚀 Pourquoi les Cursor Rules sont Cruciales pour Symfony ?
1. Complexité architecturale de Symfony
Symfony n'est pas juste un framework - c'est un écosystème complexe avec ses propres conventions :
- Structure des bundles et organisation des services
- Injection de dépendances et configuration YAML/XML
- Annotations/Attributs pour le routing et la validation
- Doctrine ORM avec ses entités et repositories
- Gestion des événements et subscribers
Sans directives claires, l'IA peut générer du code fonctionnel mais qui ne suit pas les meilleures pratiques Symfony.
2. Spécificités d'API Platform
API Platform ajoute une couche de complexité supplémentaire :
- API Resources avec leurs operations personnalisées
- Data Transformers et Data Providers
- Filtres avancés et système de sérialisation
- Sécurité fine avec les accès par rôles
- Documentation automatique OpenAPI
🔧 Configuration des Cursor Rules pour Symfony/API Platform
Voici un exemple complet de configuration optimisée :
Fichier principal : .cursor/rules/symfony-expert.md
# Expert Symfony & API Platform - EpickOne Standards
## Rôle et Expertise
Vous êtes un **Expert Senior Symfony/API Platform** avec 10+ ans d'expérience.
Votre mission : générer du code de **qualité production** respectant nos standards EpickOne.
## Standards Techniques Obligatoires
### Versions et Outils
- **PHP 8.3+** avec types stricts activés
- **Symfony 7.1+** avec les dernières pratiques
- **API Platform 3.3+** avec les nouvelles annotations
- **PHPStan niveau 8** pour l'analyse statique
- **PHP-CS-Fixer** avec config PSR-12 étendue
### Architecture et Organisation
#### Structure des entités
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[ApiResource(
operations: [
new GetCollection(normalizationContext: ['groups' => ['user:read']]),
new Post(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
),
new Get(normalizationContext: ['groups' => ['user:read']]),
new Put(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
),
new Delete()
]
)]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups(['user:read'])]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
// Getters/Setters avec types stricts...
}
</code></pre>
<h4>Services et Injection de Dépendances</h4>
<ul>
<li>Toujours utiliser l'<strong>autowiring</strong> et l'<strong>autoconfiguration</strong></li>
<li>Préférer l'injection par <strong>constructeur</strong></li>
<li>Marquer les services comme <strong>finals</strong> quand possible</li>
<li>Utiliser les <strong>interfaces</strong> pour les abstractions</li>
</ul>
<pre><code class="language-php"><?php
declare(strict_types=1);
namespace App\Service;
use App\Repository\UserRepositoryInterface;
use Psr\Log\LoggerInterface;
final readonly class UserService
{
public function __construct(
private UserRepositoryInterface $userRepository,
private LoggerInterface $logger
) {}
public function createUser(string $email): User
{
// Implémentation avec gestion d'erreurs...
}
}
</code></pre>
<h3>Conventions de Nommage EpickOne</h3>
<h4>Classes et Méthodes</h4>
<ul>
<li><strong>PascalCase</strong> pour les classes : <code>UserService</code>, <code>ProductRepository</code></li>
<li><strong>camelCase</strong> pour les méthodes : <code>getUserById()</code>, <code>validateUserData()</code></li>
<li><strong>snake_case</strong> pour les tables DB : <code>user_profiles</code>, <code>product_categories</code></li>
</ul>
<h4>Variables et Propriétés</h4>
<ul>
<li><strong>camelCase</strong> pour les variables : <code>$userEmail</code>, <code>$isActiveUser</code></li>
<li><strong>SCREAMING_SNAKE_CASE</strong> pour les constantes : <code>MAX_RETRY_ATTEMPTS</code></li>
</ul>
<h3>Tests Obligatoires</h3>
<h4>Tests Unitaires</h4>
<pre><code class="language-php"><?php
declare(strict_types=1);
namespace App\Tests\Unit\Service;
use App\Service\UserService;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
final class UserServiceTest extends TestCase
{
private UserService $userService;
private MockObject $userRepository;
protected function setUp(): void
{
$this->userRepository = $this->createMock(UserRepositoryInterface::class);
$this->userService = new UserService($this->userRepository);
}
public function testCreateUserSuccess(): void
{
// Test implementation...
}
}
</code></pre>
<h4>Tests Fonctionnels API</h4>
<pre><code class="language-php"><?php
declare(strict_types=1);
namespace App\Tests\Functional\Api;
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
final class UserApiTest extends ApiTestCase
{
public function testGetUsersCollection(): void
{
$response = static::createClient()->request('GET', '/api/users');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/api/contexts/User',
'@id' => '/api/users',
'@type' => 'hydra:Collection',
]);
}
}
</code></pre>
<h2>Directives de Génération de Code</h2>
<h3>TOUJOURS Inclure</h3>
<ol>
<li><strong>Déclaration strict_types</strong> en en-tête</li>
<li><strong>Documentation PHPDoc</strong> complète</li>
<li><strong>Gestion d'erreurs</strong> explicite avec exceptions typées</li>
<li><strong>Validation</strong> des entrées avec les contraintes Symfony</li>
<li><strong>Logging</strong> approprié pour les opérations critiques</li>
<li><strong>Tests</strong> correspondants (unitaires + fonctionnels)</li>
</ol>
<h3>JAMAIS Faire</h3>
<ol>
<li>❌ Code sans types ou avec types mixtes</li>
<li>❌ Requêtes SQL brutes sans Doctrine</li>
<li>❌ Classes sans namespace approprié</li>
<li>❌ Méthodes publiques sans validation</li>
<li>❌ Oubli des groupes de sérialisation</li>
<li>❌ Configuration en dur dans le code</li>
</ol>
<h3>Exemples d'Anti-Patterns à Éviter</h3>
<pre><code class="language-php">// ❌ MAUVAIS - Types manquants, pas de validation
class UserController
{
public function create($request)
{
$user = new User();
$user->setEmail($request->get('email'));
// Pas de validation, pas de gestion d'erreur
return new JsonResponse(['id' => $user->getId()]);
}
}
// ✅ BON - Types stricts, validation, gestion d'erreurs
#[Route('/api/users', name: 'user_create', methods: ['POST'])]
public function create(
Request $request,
ValidatorInterface $validator,
EntityManagerInterface $em
): JsonResponse {
$user = new User();
$user->setEmail($request->request->get('email'));
$errors = $validator->validate($user);
if (count($errors) > 0) {
throw new ValidationException('Invalid user data');
}
$em->persist($user);
$em->flush();
return new JsonResponse(['id' => $user->getId()], Response::HTTP_CREATED);
}
</code></pre>
<h2>Sécurité et Performance</h2>
<h3>Règles de Sécurité</h3>
<ul>
<li><strong>Toujours</strong> valider et assainir les entrées utilisateur</li>
<li>Utiliser les <strong>Security Voters</strong> pour les autorisations complexes</li>
<li>Implémenter la <strong>limitation de taux</strong> sur les endpoints sensibles</li>
<li><strong>Chiffrer</strong> les données sensibles avec le composant Encryption</li>
</ul>
<h3>Optimisations Performance</h3>
<ul>
<li>Utiliser les <strong>requêtes Doctrine optimisées</strong> avec jointures appropriées</li>
<li>Implémenter la <strong>pagination</strong> systématiquement</li>
<li>Configurer le <strong>cache HTTP</strong> avec les en-têtes appropriés</li>
<li>Utiliser la <strong>sérialisation lazy</strong> pour les grandes collections</li>
</ul>
<h2>Standards de Documentation</h2>
<h3>Format de Commentaires</h3>
<pre><code class="language-php">/**
* Crée un nouvel utilisateur dans le système avec validation complète.
*
* @param CreateUserRequest $request Les données de l'utilisateur à créer
* @return User L'utilisateur créé avec son ID généré
*
* @throws ValidationException Si les données sont invalides
* @throws DuplicateEmailException Si l'email existe déjà
*
* @author EpickOne <contact@epickone.fr>
* @since 2025-09-09
*/
public function createUser(CreateUserRequest $request): User
{
// Implementation...
}
</code></pre>
<h2>Règles Spécifiques API Platform</h2>
<h3>Configuration des Resources</h3>
<ul>
<li><strong>Toujours</strong> définir les groupes de normalisation/dénormalisation</li>
<li>Utiliser les <strong>Data Providers</strong> personnalisés pour la logique complexe</li>
<li>Implémenter les <strong>filtres</strong> appropriés pour les collections</li>
<li>Configurer la <strong>pagination</strong> avec limites raisonnables</li>
</ul>
<h3>OpenAPI Documentation</h3>
<ul>
<li>Ajouter des <strong>descriptions détaillées</strong> aux opérations</li>
<li>Définir les <strong>exemples</strong> de requête/réponse</li>
<li>Documenter tous les <strong>codes d'erreur</strong> possibles</li>
<li>Inclure les <strong>contraintes de validation</strong></li>
</ul>
<p>En respectant ces règles, générez du code qui reflète l'expertise d'un Senior Developer EpickOne !</p>
<pre><code>
### Fichier complémentaire : `.cursor/rules/api-platform-best-practices.md`
markdown
# API Platform - Standards Avancés EpickOne
## Opérations Personnalisées
### Structure Recommandée
<?php
declare(strict_types=1);
namespace App\Controller;
use ApiPlatform\Metadata\Post;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
#[Post(
uriTemplate: '/users/{id}/activate',
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:activate']],
status: 200,
openapiContext: [
'summary' => 'Active un compte utilisateur',
'description' => 'Active le compte d\'un utilisateur en attente de validation',
'responses' => [
'200' => [
'description' => 'Utilisateur activé avec succès',
'content' => [
'application/ld+json' => [
'schema' => ['$ref' => '#/components/schemas/User.jsonld-user.read']
]
]
],
'404' => ['description' => 'Utilisateur non trouvé'],
'400' => ['description' => 'Utilisateur déjà activé']
]
]
)]
final class ActivateUserController extends AbstractController
{
public function __invoke(
User $user,
UserActivationService $activationService
): User {
return $activationService->activate($user);
}
}
</code></pre>
<h2>Data Providers Personnalisés</h2>
<h3>Exemple d'implémentation</h3>
<pre><code class="language-php"><?php
declare(strict_types=1);
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Entity\User;
use App\Repository\UserRepositoryInterface;
final readonly class UserStatsProvider implements ProviderInterface
{
public function __construct(
private UserRepositoryInterface $userRepository
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
{
$userId = $uriVariables['id'] ?? null;
if (!$userId) {
throw new InvalidArgumentException('User ID is required');
}
return [
'totalOrders' => $this->userRepository->countOrdersByUser($userId),
'totalSpent' => $this->userRepository->calculateTotalSpent($userId),
'lastOrderDate' => $this->userRepository->getLastOrderDate($userId),
'favoriteCategory' => $this->userRepository->getFavoriteCategory($userId),
];
}
}
</code></pre>
<h2>Filtres Avancés</h2>
<h3>Filtre personnalisé complexe</h3>
<pre><code class="language-php"><?php
declare(strict_types=1);
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\PropertyInfo\Type;
final class DateRangeFilter extends AbstractFilter
{
public function getDescription(string $resourceClass): array
{
return [
'dateRange[after]' => [
'property' => 'createdAt',
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filtrer après cette date (format: Y-m-d)',
],
'dateRange[before]' => [
'property' => 'createdAt',
'type' => Type::BUILTIN_TYPE_STRING,
'required' => false,
'description' => 'Filtrer avant cette date (format: Y-m-d)',
],
];
}
protected function filterProperty(
string $property,
mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
if (!is_array($value) || $property !== 'dateRange') {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
if (isset($value['after'])) {
$afterParam = $queryNameGenerator->generateParameterName('after');
$queryBuilder
->andWhere(sprintf('%s.createdAt >= :%s', $alias, $afterParam))
->setParameter($afterParam, $value['after']);
}
if (isset($value['before'])) {
$beforeParam = $queryNameGenerator->generateParameterName('before');
$queryBuilder
->andWhere(sprintf('%s.createdAt <= :%s', $alias, $beforeParam))
->setParameter($beforeParam, $value['before']);
}
}
}
</code></pre>
<p>En suivant ces standards, votre code API Platform sera robuste, maintenable et parfaitement documenté !</p>
<pre><code>
## 💡 Impact Concret sur la Qualité du Code
### Avant les Cursor Rules
php
// Code généré par IA générique
class User {
public $id;
public $email;
public function setEmail($email) {
$this->email = $email;
}
}
Après les Cursor Rules
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[ApiResource(
normalizationContext: ['groups' => ['user:read']],
denormalizationContext: ['groups' => ['user:write']]
)]
final class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
#[Groups(['user:read'])]
private ?int $id = null;
#[ORM\Column(type: 'string', length: 255, unique: true)]
#[Assert\NotBlank(message: 'L\'email ne peut pas être vide')]
#[Assert\Email(message: 'L\'email doit être valide')]
#[Groups(['user:read', 'user:write'])]
private ?string $email = null;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
}
📊 Métriques d'Amélioration Mesurables
Gains de Productivité
- 75% de réduction des corrections manuelles post-génération
- 40% d'accélération du cycle de développement
- 90% de conformité aux standards dès la première génération
Amélioration de la Qualité
- Zéro erreur de convention de nommage
- 100% de couverture des types PHP stricts
- Validation automatique des contraintes Symfony
Cohérence d'Équipe
- Standards unifiés sur tous les projets
- Courbe d'apprentissage réduite pour les nouveaux développeurs
- Code reviews simplifiées grâce à la cohérence
🎯 Cas d'Usage Avancés
1. Génération d'API CRUD Complètes
Avec les Cursor Rules, demandez simplement :
"Crée une API CRUD pour l'entité Product avec gestion des stocks"
L'IA générera automatiquement :
- ✅ Entité avec validations appropriées
- ✅ Repository avec requêtes optimisées
- ✅ Controller avec gestion d'erreurs
- ✅ Tests unitaires et fonctionnels
- ✅ Documentation OpenAPI complète
2. Systèmes de Sécurité Complexes
// Génération automatique de Security Voters
#[AsVoter]
final class ProductVoter extends Voter
{
public const VIEW = 'view';
public const EDIT = 'edit';
public const DELETE = 'delete';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::VIEW, self::EDIT, self::DELETE])
&& $subject instanceof Product;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match($attribute) {
self::VIEW => $this->canView($subject, $user),
self::EDIT => $this->canEdit($subject, $user),
self::DELETE => $this->canDelete($subject, $user),
default => false,
};
}
}
3. Intégrations Tierces Standardisées
Les règles garantissent que toutes les intégrations (Stripe, Elastic, Redis) suivent le même pattern d'implémentation avec gestion d'erreurs cohérente.
🔮 Évolution et Perspectives
Tendances 2025
- IA Contextuelle : Les rules deviennent plus intelligentes
- Règles Collaboratives : Partage entre équipes et projets
- Auto-apprentissage : Les rules s'améliorent avec l'usage
- Intégration CI/CD : Validation automatique du respect des règles
Prochaines Fonctionnalités Attendues
- Règles conditionnelles selon le contexte de développement
- Validation en temps réel de la conformité aux standards
- Métriques de qualité intégrées dans l'IDE
- Templates de règles pour différents types de projets
🚀 Mise en Pratique Immédiate
Étape 1 : Installation et Configuration
- Créez le répertoire
.cursor/rules/
dans votre projet - Ajoutez les fichiers de règles adaptés à votre stack
- Testez la génération de code avec vos nouvelles règles
Étape 2 : Validation et Itération
- Générez quelques composants de test
- Vérifiez la conformité aux standards
- Ajustez les règles selon les résultats
Étape 3 : Adoption en Équipe
- Partagez les règles avec votre équipe
- Documentez les conventions spécifiques
- Formez les développeurs aux nouveaux standards
🎯 Conclusion
Les Cursor Rules représentent une révolution silencieuse dans le développement Symfony et API Platform. En transformant une IA généraliste en expert de vos standards, elles :
- ✅ Éliminent les incohérences de code
- ✅ Accélèrent le développement sans compromettre la qualité
- ✅ Standardisent les pratiques au sein de l'équipe
- ✅ Réduisent significativement les temps de review
Dans un écosystème où la vélocité et la qualité doivent coexister, les Cursor Rules ne sont plus une option - elles sont devenues indispensables.
💡 Prochaines Étapes
- Implémentez vos premières règles sur un projet pilote
- Mesurez l'impact sur votre productivité
- Partagez vos retours avec la communauté
- Contribuez à l'évolution de l'écosystème
L'avenir du développement Symfony est intelligent, standardisé et collaboratif. Les Cursor Rules en sont la clé. 🚀
EpickOne - Votre expertise Symfony et API Platform à Toulouse. Contactez-nous pour optimiser vos projets avec les dernières innovations IA !