---
# System prepended metadata

title: Symfony - Créer un nouveau projet
tags: [Symfony, gobelins]

---

# 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)
```