Recherche…
Travailler avec l'API RESTFul
Le transfert d'état représentatif (REST) est un style architectural utilisé pour le développement Web, introduit et défini en 2000 par Roy Fielding.
Voir sur le wiki: wiki REST
Il est basé sur le protocole HTTP ( HTTP on Wiki ), les requêtes HTTP (GET, POST, PATCH, DELETE ...) / les codes réponses (404, 400, 200, 201, 500 ...) et la structure des corps.
C'est un excellent moyen d'exposer vos données à un autre système sur Internet.
Imaginez que vous souhaitiez créer une api RESTFul pour gérer votre StackOverFlower (utilisateur) sur votre base de données locale.
Prenons l'exemple!
Framework Symfony 2.8
- Serveur Web :
Vous devez installer et configurer un serveur Web sur votre machine locale, voir Wamp ou Lamp ou Mamp : vous devez avoir une version récente de PHP ( !!! Exigences Symfony !!! )
- Php cli et compositeur:
Vous devez configurer PHP cli (variable selon notre système), tapez ce "PHP cli [OS-NAME] How-to" dans notre ami Google! Vous devez installer le composeur, voir Composer install
- Symfony:
Vous devez installer Symfony 2.8 (avec compositeur, c'est mieux), ouvrez un terminal (ou cmd sur Windows) et accédez au chemin de votre serveur Web.
Symfony 2 fonctionne avec l'un des meilleurs types de structure: Bundles. Tous sont des paquets sur Symfony! Nous pouvons le tester ci-dessus.
cd /your-web-server-path/
composer create-project symfony/framework-standard-edition example "2.8.*"
Allez dans l'arborescence voir: Symfony 2.8 est installé sur le répertoire "example".
- FOSRest (for FriendsOfSymfony) sur JMSSerializer Bundle:
Vous devez installer ces deux bundles:
JMSSerializer ( Install ):
composer require jms/serializer-bundle "~0.13"
FosRestBundle ( Install ):
composer require friendsofsymfony/rest-bundle
N'oubliez pas de les activer dans AppKernel.php!
- Configuration de base:
Créez votre propre ensemble "Exemple" et créez la base de données.
cd /path/to/your/symfony/
php app/console generate:bundle
php app/console doctrine:generate:database
Allez au bas du fichier de configuration de votre application Symfony 2.8 et collez-le:
#app/config/config.yml
fos_rest:
format_listener:
rules:
- { path: '^/stackoverflower', priorities: ['xml', 'json'], fallback_format: xml, prefer_extension: true }
- { path: '^/', priorities: [ 'text/html', '*/*'], fallback_format: html, prefer_extension: true }
Faites votre répertoire de doctrine ("example / src / ExampleBundle / Entity") et le fichier de ressources ("StackOverFlower.orm.yml"):
# src/ExampleBundle/Resources/config/doctrine/StackOverFlower.orm.yml
ExampleBundle\Entity\StackOverFlower:
type: entity
table: stackoverflower
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
name:
type: string
length: 100
Générer un schéma d'entité et de mise à jour:
php app/console doctrine:generate:entity StackOverFlower
php app/console doctrine:schema:update --force
Faire un contrôleur par défaut:
#src/ExampleBundle/Controller/StackOverFlowerController.php
namespace ExampleBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\Get;
use FOS\RestBundle\Controller\Annotations\Post;
use FOS\RestBundle\Controller\Annotations\Delete;
use ExampleBundle\Entity\StackOverFlower;
class StackOverFlowerController extends FOSRestController
{
/**
* findStackOverFlowerByRequest
*
* @param Request $request
* @return StackOverFlower
* @throws NotFoundException
*/
private function findStackOverFlowerByRequest(Request $request) {
$id = $request->get('id');
$user = $this->getDoctrine()->getManager()->getRepository("ExampleBundle:StackOverFlower")->findOneBy(array('id' => $id));
return $user;
}
/**
* validateAndPersistEntity
*
* @param StackOverFlower $user
* @param Boolean $delete
* @return View the view
*/
private function validateAndPersistEntity(StackOverFlower $user, $delete = false) {
$template = "ExampleBundle:StackOverFlower:example.html.twig";
$validator = $this->get('validator');
$errors_list = $validator->validate($user);
if (count($errors_list) == 0) {
$em = $this->getDoctrine()->getManager();
if ($delete === true) {
$em->remove($user);
} else {
$em->persist($user);
}
$em->flush();
$view = $this->view($user)
->setTemplateVar('user')
->setTemplate($template);
} else {
$errors = "";
foreach ($errors_list as $error) {
$errors .= (string) $error->getMessage();
}
$view = $this->view($errors)
->setTemplateVar('errors')
->setTemplate($template);
}
return $view;
}
/**
* newStackOverFlowerAction
*
* @Get("/stackoverflower/new/{name}")
*
* @param Request $request
* @return String
*/
public function newStackOverFlowerAction(Request $request)
{
$user = new StackOverFlower();
$user->setName($request->get('name'));
$view = $this->validateAndPersistEntity($user);
return $this->handleView($view);
}
/**
* editStackOverFlowerAction
*
* @Get("/stackoverflower/edit/{id}/{name}")
*
* @param Request $request
* @return type
*/
public function editStackOverFlowerAction(Request $request) {
$user = $this->findStackOverFlowerByRequest($request);
if (! $user) {
$view = $this->view("No StackOverFlower found for this id:". $request->get('id'), 404);
return $this->handleView($view);
}
$user->setName($request->get('name'));
$view = $this->validateAndPersistEntity($user);
return $this->handleView($view);
}
/**
* deleteStackOverFlowerAction
*
* @Get("/stackoverflower/delete/{id}")
*
* @param Request $request
* @return type
*/
public function deleteStackOverFlowerAction(Request $request) {
$user = $this->findStackOverFlowerByRequest($request);
if (! $user) {
$view = $this->view("No StackOverFlower found for this id:". $request->get('id'), 404);
return $this->handleView();
}
$view = $this->validateAndPersistEntity($user, true);
return $this->handleView($view);
}
/**
* getStackOverFlowerAction
*
* @Get("/stackoverflowers")
*
* @param Request $request
* @return type
*/
public function getStackOverFlowerAction(Request $request) {
$template = "ExampleBundle:StackOverFlower:example.html.twig";
$users = $this->getDoctrine()->getManager()->getRepository("ExampleBundle:StackOverFlower")->findAll();
if (count($users) === 0) {
$view = $this->view("No StackOverFlower found.", 404);
return $this->handleView();
}
$view = $this->view($users)
->setTemplateVar('users')
->setTemplate($template);
return $this->handleView($view);
}
}
Faites votre vue Twig par défaut:
#src/ExampleBundle/Resources/views/StackOverFlower.html.twig
{% if errors is defined %}
{{ errors }}
{% else %}
{% if users is defined %}
{{ users | serialize }}
{% else %}
{{ user | serialize }}
{% endif %}
{% endif %}
Vous venez de faire votre première API RESTFul!
Vous pouvez le tester sur: http: //votre-nom-serveur/votre-symfony-path/app_dev.php/stackoverflower/new/test .
Comme vous pouvez le voir dans la base de données, un nouvel utilisateur a été créé avec le nom "test".
Vous pouvez obtenir la liste des stackoverflower sur: http: //votre-nom-serveur/votre-symfony-path/app_dev.php/stackoverflowers
Vous avez un exemple complet sur mon compte github de cet exemple: exemple Git Hub , sur la branche "master" de cet exemple, et sur la branche "real-routes" un exemple avec une URL plus appropriée (comme POST et DELETE).
A plus tard pour un exemple avec SOAP!
Meilleures salutations,
Mathieu
Travailler avec l'API SOAP
SOAP (Simple Access Object Protocol) est basé sur XML, comme XML-RPC, est ancêtre , avec un fichier appelé WSDL , qui décrit la méthode à exposer.
Ce protocole est souvent basé sur SOAP-Enveloppe , un SOAP-Body , ou encore SOAP-Header , les données sont enveloppées dans une structure et interprétées de la même manière à partir de différentes langues.
Pour plus d'informations, voir: SOAP sur wiki
Comme décrit ci-dessus, le plus important pour décrire votre service Web est le fichier WSDL , voir: explication WSDL sur le wiki
La base du travail sera de définir ce qui est exposé sur votre API SOAP, votre classe et votre processus métier seront automatiquement gérés par la classe PHP SOAPServer de base. Vous avez toujours besoin du code!
Voyons comment le fichier est construit:
- Service: Définit l'URI de l'API et ce qui sera associé.
- Liaison: elle définit les opérations associées au service
- Opérations: certaines méthodes que vous souhaitez exposer au Web
- PortTypes: définir des requêtes et des réponses
- Demandes et réponses: ce que vous attendez d'entrée et de sortie
- Messages: quelle forme attendez-vous (paramètres) sur chaque IO, ils peuvent être simples (string, integer, float ...) ou complexes (format structuré)
Avec ces informations de base, vous pouvez atteindre toutes les API que vous souhaitez.
Imaginez que vous souhaitiez créer une API SOAP pour gérer votre StackOverFlower (utilisateur) sur votre base de données locale.
Prenons l'exemple!
Installez le serveur Web, Php cli, Composer, Symfony 2.8, créez un nouvel ensemble "ExampleBundle" et créez le schéma comme décrit ci-dessus.
Avant de commencer à construire notre logique métier, nous devions savoir quoi exposer de notre contrôleur. Ce travail est effectué en utilisant le WSDL. Ceci est un exemple d'une bonne syntaxe d'un WSDL:
<definitions name="StackOverFlowerService"
targetNamespace="http://example/soap/stackoverflower.wsdl"
xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://example/soap/stackoverflower.wsdl"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<message name="NewRequest">
<part name="name" type="xsd:string"/>
</message>
<message name="NewResponse">
<part name="status" type="xsd:string"/>
</message>
<message name="getListRequest"></message>
<message name="getListResponse">
<part name="list" type="xsd:string"/>
</message>
<message name="editRequest">
<part name="id" type="xsd:string"/>
<part name="name" type="xsd:string"/>
</message>
<message name="editResponse">
<part name="status" type="xsd:string"/>
</message>
<message name="deleteRequest">
<part name="id" type="xsd:string"/>
</message>
<message name="deleteResponse">
<part name="status" type="xsd:string"/>
</message>
<portType name="StackOverFlower_PortType">
<operation name="newStack">
<input message="tns:NewRequest"/>
<output message="tns:NewResponse"/>
</operation>
<operation name="getList">
<input message="tns:getListRequest"/>
<output message="tns:getListResponse"/>
</operation>
<operation name="edit">
<input message="tns:editRequest"/>
<output message="tns:editResponse"/>
</operation>
<operation name="delete">
<input message="tns:deleteRequest"/>
<output message="tns:deleteResponse"/>
</operation>
</portType>
<binding name="StackOverFlower_Binding" type="tns:StackOverFlower_PortType">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="newStack">
<soap:operation soapAction="newStack"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:new"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:new"
use="encoded"/>
</output>
</operation>
<operation name="getList">
<soap:operation soapAction="getList"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:get-list"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:get-list"
use="encoded"/>
</output>
</operation>
<operation name="edit">
<soap:operation soapAction="edit"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:edit"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:edit"
use="encoded"/>
</output>
</operation>
<operation name="delete">
<soap:operation soapAction="delete"/>
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:delete"
use="encoded"/>
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:example:delete"
use="encoded"/>
</output>
</operation>
</binding>
<service name="StackOverFlower_Service">
<documentation>Description File of StackOverFlowerService</documentation>
<port binding="tns:StackOverFlower_Binding" name="StackOverFlower_Port">
<soap:address
location="http://example/stackoverflower/" />
</port>
</service>
</definitions>
Nous devons prendre cela sur votre répertoire web symfony (dans le sous-répertoire soap et nommez-le "stackoverflower.wsdl").
Vraiment inspiré de WSDl Exemple . Vous pouvez valider cela avec un validateur WSDl en ligne
Après cela, nous pouvons rendre notre service et contrôleur de base inspiré de SOAP Symfony 2.8 Doc .
Service géré par PHP SOAPServer:
#src\ExampleBundle\Services\StackOverFlowerService.php
namespace ExampleBundle\Services;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use ExampleBundle\Entity\StackOverFlower;
class StackOverFlowerService
{
private $em;
private $stackoverflower;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function newStack($name)
{
$stackoverflower = new StackOverFlower();
$stackoverflower->setName($name);
$this->em->persist($stackoverflower);
$this->em->flush();
return "ok";
}
public function getList()
{
$stackoverflowers = $this->em->getRepository("ExampleBundle:StackOverFlower")->findAll();
$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders);
return $serializer->serialize($stackoverflowers, 'json');
}
public function edit($id, $name)
{
$stackoverflower = $this->em->getRepository("ExampleBundle:StackOverFlower")->findOneById($id);
$stackoverflower->setName($name);
$this->em->persist($stackoverflower);
$this->em->flush();
return "ok";
}
public function delete($id)
{
$stackoverflower = $this->em->getRepository("ExampleBundle:StackOverFlower")->findOneById($id);
$this->em->remove($stackoverflower);
$this->em->flush();
return "ok";
}
}
Configurez ce service:
#src\ExampleBundle\Resources\config\services.yml
services:
stackoverflower_service:
class: ExampleBundle\Services\StackOverFlowerService
arguments: [@doctrine.orm.entity_manager]
Comme vous pouvez le voir, nous injectons la Doctrine Entity Manger comme une dépendance car nous devons l'utiliser pour CRUD StackOverFlower Object.
Contrôleur, qui expose l'objet de service:
#src\ExampleBundle\Controller\StackOverFlowerController.php
namespace ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class StackOverFlowerController extends Controller
{
public function indexAction()
{
ini_set("soap.wsdl_cache_enabled", "0");
$options = array(
'uri' => 'http://example/app_dev.php/soap',
'cache_wsdl' => WSDL_CACHE_NONE,
'exceptions' => true
);
$server = new \SoapServer(dirname(__FILE__).'/../../../**web/soap/stackoverflower.wsdl**', $options);
$server->setObject($this->get('stackoverflower_service'));
$response = new Response();
$response->headers->set('Content-Type', 'text/xml; charset=utf-8');
ob_start();
$server->handle();
$response->setContent(ob_get_clean());
return $response;
}
}
Pour en savoir plus sur les services, voir: Conteneur de services sur le doc Symfony
La route :
example_soap:
path: /soap
defaults: { _controller: ExampleBundle:StackOverFlower:index }
Le modèle de base de la brindille:
#src\ExampleBundle\Resources\views\Soap\default.html.twig
{% if status is defined %}
{{ status }}
{% else %}
{{ list }}
{% endif %}
Nous avons créé votre première API SOAP avec Symfony 2.8!
Avant de l'exposer, il faut tester !!
Dans votre StackOverFlowerController, ajoutez ceci:
public function testNewAction(Request $request)
{
$service = $this->get('stackoverflower_service');
$result = $service->newStack($request->query->get('name'));
return $this->render('ExampleBundle:Soap:default.html.twig', array('status' => $result));
}
public function testEditAction(Request $request)
{
$service = $this->get('stackoverflower_service');
$result = $service->edit($request->query->get('id'), $request->query->get('name'));
return $this->render('ExampleBundle:Soap:default.html.twig', array('status' => $result));
}
public function testGetListAction(Request $request)
{
$service = $this->get('stackoverflower_service');
$result = $service->getList();
return $this->render('ExampleBundle:Soap:default.html.twig', array('list' => $result));
}
public function testDeleteAction(Request $request)
{
$service = $this->get('stackoverflower_service');
$result = $service->delete($request->query->get('id'));
return $this->render('ExampleBundle:Soap:default.html.twig', array('list' => $result));
}
// To test this from an another server, you can type this :
// $client = new \SoapClient("http://example/app_dev.php/soap?wsdl", array("trace" => 1, "exception" => 1));
// $result = $client->newStack($request->query->get('name'));
// print_r($result);
Les routes:
test_new:
path: /stackoverflower/new
defaults: { _controller: ExampleBundle:StackOverFlower:testNew }
test_edit:
path: /stackoverflower/edit
defaults: { _controller: ExampleBundle:StackOverFlower:testEdit }
test_get_list:
path: /stackoverflower/get-list
defaults: { _controller: ExampleBundle:StackOverFlower:testGetList }
test_delete:
path: /stackoverflower/delete
defaults: { _controller: ExampleBundle:StackOverFlower:testDelete }
Vous pouvez taper ceci dans votre navigateur:
Ceci est un exemple très basique d'une API non sécurisée avec SOAP, je peux faire un exemple d'exemple sécurisé derrière une authentification par clé api ultérieurement.
Que tous les gens ...
Mathieu