On va créer une application Silex avec une table User. L'application va disposer d'un API REST pour l'entité User.
Elle disposera des méthodes suivantes :
- - affiche tous
- - affiche un
- - créer
- - modifier
- - supprimer
Attention, cet exemple est une implémentation à minima et n'est donc pas sécurisé : Ne pas utiliser en prod.
1 - Création de l'application Silex
On commence par créer un vhost et une base de données. Puis on va créer un dossier pour notre application web et dans ce dernier on va ajouter un fichier composer.json.
Je passe sur les étapes de création de vhost et de db (cf. les autres tutoriels du site). On passe directement à la création du fichier composer.json :
nano composer.json
{
"require": {
"silex/silex": "1.3.*",
"doctrine/dbal": "2.5.*"
},
"autoload" : {
"psr-4": {"SilexApi\\": "src"}
}
}
Installation de composer.phar et des vendors :
curl -s https://getcomposer.org/installer | php
php compser.phar install
On va ensuite créer les répertoires qui vont structurer notre app :
mkdir app app/config src web
Création de la base de données :
mkdir db
nano db/database.sql
create database if not exists silex_api character set utf8 collate utf8_unicode_ci; use silex_api; drop table if exists user; create table user ( id integer not null primary key auto_increment, firstname varchar(255) not null, lastname varchar(255) not null ) engine=innodb character set utf8 collate utf8_unicode_ci; insert into user values (1, 'Jean', 'Duchmol'); insert into user values (2, 'Sophie', 'Tartempion');
Exécution du script de création de db :
mysql -u root -p silex_api < db/database.sql
2 - Création du front controller
On crée le front controller dans le dossier web :
nano web/index.php
<?php
require_once __DIR__.'/../vendor/autoload.php';
$app = new Silex\Application();
require __DIR__.'/../app/config/dev.php';
require __DIR__.'/../app/app.php';
require __DIR__.'/../app/routes.php';
$app->run();
Ajout du fichier .htaccess pour l'url rewriting :
nano web/.htaccess
DirectoryIndex index.php
RewriteEngine On
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .? - [L]
RewriteRule .? %{ENV:BASE}/index.php [L]
RedirectMatch 302 ^/$ /index.php/
3 - Création du noyau de l'app
nano app/app.php
<?php
use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Component\HttpFoundation\Request;
ErrorHandler::register();
ExceptionHandler::register();
$app->register(new Silex\Provider\DoctrineServiceProvider());
$app['dao.user'] = $app->share(function ($app) {
return new SilexApi\UserDao($app['db']);
});
// Register JSON data decoder for JSON requests
$app->before(function (Request $request) {
if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) {
$data = json_decode($request->getContent(), true);
$request->request->replace(is_array($data) ? $data : array());
}
});
4 - Création des fichiers de configuration
nano app/config/dev.php
<?php
// Doctrine (db)
$app['db.options'] = array(
'driver' => 'pdo_mysql',
'charset' => 'utf8',
'host' => '127.0.0.1', // Mandatory for PHPUnit testing
'port' => '3306',
'dbname' => 'SilexApi',
'user' => 'user',
'password' => 'pwd',
);
// enable the debug mode
$app['debug'] = true;
nano app/config/prod.php
<?php
// Doctrine (db)
$app['db.options'] = array(
'driver' => 'pdo_mysql',
'charset' => 'utf8',
'host' => 'localhost',
'port' => '3306',
'dbname' => 'SilexApi',
'user' => 'user',
'password' => 'pwd'
);
5 - Création du model pour la table User
Notre table user va comporter les champs : "id", "firstname" et "lastname".
On va d'abord créer un model pour cette table :
nano src/User.php
<?php
namespace SilexApi;
class User
{
/**
* @var integer
*/
private $id;
/**
* @var string
*/
private $firstname;
/**
* @var string
*/
private $lastname;
/**
* @return int
*/
public function getId() {
return $this->id;
}
/**
* @param int $id
*/
public function setId( $id ) {
$this->id = $id;
}
/**
* @return string
*/
public function getFirstname() {
return $this->firstname;
}
/**
* @param string $firstname
*/
public function setFirstname( $firstname ) {
$this->firstname = $firstname;
}
/**
* @return string
*/
public function getLastname() {
return $this->lastname;
}
/**
* @param string $lastname
*/
public function setLastname( $lastname ) {
$this->lastname = $lastname;
}
}
On crée ensuite une classe pour l'appel des données :
nano src/UserDAO.php
<?php
namespace SilexApi;
use Doctrine\DBAL\Connection;
class UserDao
{
private $db;
public function __construct(Connection $db)
{
$this->db = $db;
}
protected function getDb()
{
return $this->db;
}
public function findAll()
{
$sql = "SELECT * FROM user";
$result = $this->getDb()->fetchAll($sql);
$entities = array();
foreach ( $result as $row ) {
$id = $row['id'];
$entities[$id] = $this->buildDomainObjects($row);
}
return $entities;
}
public function find($id)
{
$sql = "SELECT * FROM user WHERE id=?";
$row = $this->getDb()->fetchAssoc($sql, array($id));
if ($row) {
return $this->buildDomainObjects($row);
} else {
throw new \Exception("No user matching id ".$id);
}
}
public function save(User $user)
{
$userData = array(
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastname()
);
// TODO CHECK
if ($user->getId()) {
$this->getDb()->update('user', $userData, array('id' => $user->getId()));
} else {
$this->getDb()->insert('user', $userData);
$id = $this->getDb()->lastInsertId();
$user->setId($id);
}
}
public function delete($id)
{
$this->getDb()->delete('user', array('id' => $id));
}
protected function buildDomainObjects($row)
{
$user = new User();
$user->setId($row['id']);
$user->setFirstname($row['firstname']);
$user->setLastname($row['lastname']);
return $user;
}
}
6 - Créations des routes/controllers
nano app/routes.php
<?php
use Symfony\Component\HttpFoundation\Request;
use SilexApi\User;
// Get all users
$app->get('/api/users', function () use ($app) {
$users = $app['dao.user']->findAll();
$responseData = array();
foreach ($users as $user) {
$responseData[] = array(
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastName()
);
}
return $app->json($responseData);
})->bind('api_users');
// Get on user
$app->get('/api/user/{id}', function ($id, Request $request) use ($app) {
$user = $app['dao.user']->find($id);
if (!isset($user)) {
$app->abort(404, 'User not exist');
}
$responseData = array(
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getFirstname()
);
return $app->json($responseData);
})->bind('api_user');
// Create user
$app->post('/api/user/create', function (Request $request) use ($app) {
if (!$request->request->has('firstname')) {
return $app->json('Missing parameter: firstname', 400);
}
if (!$request->request->has('lastname')) {
return $app->json('Missing parameter: lastname', 400);
}
$user = new User();
$user->setFirstname($request->request->get('firstname'));
$user->setLastname($request->request->get('lastname'));
$app['dao.user']->save($user);
$responseData = array(
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastname()
);
return $app->json($responseData, 201);
})->bind('api_user_add');
// Delete user
$app->delete('/api/user/delete/{id}', function ($id, Request $request) use ($app) {
$app['dao.user']->delete($id);
return $app->json('No content', 204);
})->bind('api_user_delete');
// Update user
$app->put('/api/user/update/{id}', function ($id, Request $request) use ($app) {
$user = $app['dao.user']->find($id);
$user->setFirstname($request->request->get('firstname'));
$user->setlastname($request->request->get('lastname'));
$app['dao.user']->save($user);
$responseData = array(
'id' => $user->getId(),
'firstname' => $user->getFirstname(),
'lastname' => $user->getLastname()
);
return $app->json($responseData, 202);
})->bind('api_user_update');
7 - Test de l'API
On va tester notre API avec le plug-in Firefox RESTClient (https://addons.mozilla.org/fr/firefox/addon/restclient/).
Dans le plugin, allez dans le menu "Headers" et choisissez l'option "Custom Header" et ajouter les paramètres suivants :
- name : Content-Type
- Value : application/json
Sélectionner le verbe en fonction des requêtes :
- - GET > findAll et find
- - POST > create
- - PUT > update
- - DELETE > delete