# Symfony - Créer un nouveau projet ###### tags: `Symfony` `gobelins` ## Intro - Framework développé par des français en 2006 pour du web back/front - Inspiré par Ruby - Outil cadrés, règles, best practices ## Création d'un projet ### 1. Installer symfony Utiliser la ligne de commande et créer un dossier portant le nom du projet. Se placer avant la racine et éxecuter les lignes de commandes. --> En profiter pour installer symfony --> Utiliser composer pour crééer le projet [Symfony doc](https://symfony.com/doc/current/index.html#gsc.tab=0) ### 2. initialiser le git Créer un nouveau repo et attacher le projet. [Git help](https://hackmd.io/mV6iMk4RQBOG8ij02msPyg?both) ## Configuration environnement - Coper/coller le *.env* et renomer en *.env.local* puis *.env.test.local* - Modifier le champs suivant avec les données correspondantes : ``` DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name ``` ## Lancer le projet Utiliser le local web server si on ne veut pas utiliser d'apache : [Symfony local web server](https://symfony.com/doc/current/setup/symfony_server.html#installation) Ou lancer cette commande si le server local de symfony ne fonctionne pas (erreur version php par exemple) : ``` cd public php -S localhost:8000 ``` ## Doctrine (si on a pas pris la solution website) Interface entre les entités et la sauvegarde en bdd ``` composer require symfony/orm-pack; composer require --dev symfony/maker-bundle; ``` ## Bundle Composant pour symfony. Installer le maker bundle : [MakerBundle](https://symfony.com/doc/current/bundles/SymfonyMakerBundle/index.html) ``` composer require symfony/maker-bundle --dev ``` ## Entité ### Création ``` php bin/console make:entity; ``` ### Migration ``` php bin/console make:migration; php bin/console doctrine:migrations:migrate; ``` ### Controller ``` php bin/console make:controller; ``` Le controller va contenir les routes (lien request response). Exemple de fonction type dans un controller : ``` /** * @Route("/character/index", * name="character_index", * methods={"GET","HEAD"}) */ public function index() { //On verifie l'acccès $this->denyAccessUnlessGranted('characterIndex', null); //On recupere les donnes $characters = $this->characterService->getAll(); //Return return new JsonResponse($characters); } ``` Le controller DOIT renvoyer quelque chose (sinon : erreur) ### Sécurité ``` composer require security; php bin/console make:voter; ``` Gérer sécurité d'accès à la ressource. Exemple type de code d'une classe voter : ``` class CharacterVoter extends Voter { public const CHARACTER_INDEX= 'characterIndex'; public const CHARACTER_DISPLAY= 'characterDisplay'; private const ATTRIBUTES = array( self::CHARACTER_INDEX, self::CHARACTER_DISPLAY ); //Default function protected function supports($attribute, $subject) { if (null !== $subject) { return $subject instanceof Character && in_array($attribute, self::ATTRIBUTES); } return in_array($attribute, self::ATTRIBUTES); } protected function voteOnAttribute($attribute, $subject, TokenInterface $token) { $user = $token->getUser(); switch ($attribute) { case self::CHARACTER_INDEX: case self::CHARACTER_DISPLAY: return $this->canDisplay($token, $subject); break; } throw new LogicException('Invalid attribute : ' . $attribute); } //Checks if is allowed to display (user role) private function canDisplay($token, $subject) { return true; } } ``` ### Service Action dans la bdd Exemple type de fonction dans un service : ``` public function create(string $data) { $character = new Character(); $character ->setIdentifier(hash('sha1', uniqid())) ->setCreation(new DateTime()) ->setModification(new DateTime()) ; $this->submit($character, CharacterType::class, $data); $this->isEntityFilled($character); //Sauvegarder dans la bdd $this->em->persist($character); $this->em->flush(); return $character; } ``` Submit function : ``` public function submit(Character $character, $formName, $data) { $dataArray = is_array($data) ? $data : json_decode($data, true); //Bad array if (null !== $data && !is_array($dataArray)) { throw new UnprocessableEntityHttpException('Submitted data is not an array -> ' . $data); } //Submits form $form = $this->formFactory->create($formName, $character, ['csrf_protection' => false]); $form->submit($dataArray, false); //With false, only submitted fields are validated //Gets errors $errors = $form->getErrors(); foreach ($errors as $error) { throw new LogicException('Error ' . get_class($error->getCause()) . ' --> ' . $error->getMessageTemplate() . ' ' . json_encode($error->getMessageParameters())); } } ``` isEntityFilled function : ``` public function isEntityFilled(Character $character) { $errors = $this->validator->validate($character); if (count($errors) > 0) { throw new UnprocessableEntityHttpException((string) $errors . 'Missing Data for entity -> ' . json_encode($character->toArray())); } } ``` Injecter l'interface du service ### Formulaires [Site](https://symfony.com/doc/current/forms.html) [Types des champs](https://symfony.com/doc/current/reference/forms/types.html) [Submit](https://symfony.com/doc/current/form/direct_submit.html) ``` composer require symfony/form ``` ### Validation des champs [Validator symfony](https://symfony.com/doc/current/validation.html) ``` composer require symfony/validator doctrine/annotations ``` ### Test [Intsaller phpunit](https://symfony.com/doc/current/testing.html#the-phpunit-testing-framework) ``` composer require --dev symfony/phpunit-bridge ``` Exemple type de tests : ``` class CharacterControllerTest extends WebTestCase { private $client; private $content; private static $identifier; public function setUp() { $this->client = static::createClient(); } public function testIndex() { $this->client->request('GET','/character/index'); $this->assertJsonResponse($this->client->getResponse()); } public function testCreate() { $this->client->request( 'POST', '/character/create', array(), array(), array('CONTENT_TYPE' => 'application/json'), '{ "kind":"Dame", "name":"Eldalote", "surname":"Fleur elfique", "caste":"Elfe", "knowledge":"Arts", "intelligence":120, "life":12, "player":1 }' ); $this->assertJsonResponse($this->client->getResponse()); $this->defineIdentifier(); $this->assertIdentifier(); } public function assertIdentifier() { $this->assertArrayHasKey('identifier', $this->content); } public function defineIdentifier() { self::$identifier = $this->content['identifier']; } private function assertJsonResponse($response, int $statusCode = 200) { $this->assertEquals($statusCode,$response->getStatusCode()); $this->assertTrue($response->headers->contains('Content-Type','application/json'), $response->headers); $this->content = json_decode($response->getContent(), true, 50); } } ``` Exécuter les test : ``` php bin/phpunit ``` ## Requetes A effectuer dans le repository Exemple type : ``` public function findOneByIdentifier($identifier) { return $this->createQueryBuilder('c') ->select('c', 'p') ->leftJoin('c.player', 'p') ->where('c.identifier = :identifier') ->setParameter('identifier', $identifier) ->getQuery() ->getOneOrNullResult() //retourne un player ou null si aucun player ; } ``` ## Formattage des données A faire uniquement pour l'affichage (fonction toAray dans l'entity) et conserver telle quelle les données en bdd ## Commit Créer un bon message de commit Attention aux versions (pas obligatoire à chaque fois) ## Déboguer [Dumper](https://symfony.com/doc/current/components/var_dumper.html) ## Documentation --> "http://localhost/projectName/public/api/doc" [Nelmio doc](https://symfony.com/doc/current/bundles/NelmioApiDocBundle/index.html) [Swagger doc](https://swagger.io/specification/) ``` composer require nelmio/api-doc-bundle; composer require twig; composer require asset; ``` Exemple : ``` /** * Creates the character * * ... * * @SWG\Response( * response=200, * description="success", * @Model(type=Character::class), * ) * * @SWG\Response( * response=403, * description="Acces denied", * ) * * @SWG\Parameter( * name="request", * in="body", * description="Data for character", * required=true, * @Model(type=App\Form\CharacterType::class) * ) * * @SWG\Tag(name="Character") * * ... * * @Route("/character/create", * name="character_create", * methods={"POST", "HEAD"}) */ public function create(Request $request) ```