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