Framework PHP: Alegerea între Symfony și Laravel
Publicat: 2022-03-11Astăzi, când începeți un nou proiect, una dintre deciziile cheie este alegerea cadrului potrivit. A devenit greu de imaginat construirea unei aplicații web complexe de la zero în zilele noastre fără una.
Multe limbaje populare pentru dezvoltarea web au cadrul lor „implicit”, cum ar fi Ruby on Rails pentru Ruby sau Django pentru Python. Cu toate acestea, PHP nu are o astfel de valoare implicită și are mai multe opțiuni populare din care să aleagă.
Conform Google trends și GitHub, cele mai populare cadre PHP sunt Symfony cu 13,7k stele și Laravel cu 29k stele (la momentul scrierii acestui articol).
În acest articol, voi compara aceste două cadre și vă voi arăta cum să implementați caracteristici simple, de zi cu zi cu fiecare. În acest fel, puteți compara codul exemplelor din viața reală unul lângă altul.
Acest articol presupune abilități PHP puternice și o înțelegere a paradigmei arhitecturale MVC, dar nu este necesară nicio experiență anterioară cu Symfony sau Laravel.
Concurenții
Laravel
Când vorbim despre Laravel, ne referim la Laravel versiunea 4 și mai departe. Laravel 4 a fost lansat în 2013 și a reprezentat o rescrie completă a cadrului. Funcționalitatea cadrului a fost decuplată în componente separate, care au fost gestionate cu Composer, în loc să fie totul într-un singur depozit de cod uriaș.
Laravel se declară ca un cadru pentru dezvoltare rapidă cu o sintaxă simplă și frumoasă, care este ușor de învățat, citit și întreținut. Este cel mai popular framework din 2016. Conform tendințelor Google, este de trei ori mai popular decât alte framework-uri, iar pe GitHub are de două ori mai multe stele decât concurenții.
Symfony
Symfony 2 a fost lansat în 2011, dar nu trebuie confundat cu Symfony 1, care era un cadru total diferit, cu principii de bază diferite. Fabien Potencier a creat Symfony 2, iar versiunea actuală este 3.2, care este o versiune incrementală a Symfony 2. Prin urmare, ele sunt adesea numite simplu Symfony2/3.
La fel ca Laravel 4, Symfony 2 este conceput ca un set de componente decuplate. Există două beneficii aici: putem înlocui orice componentă dintr-un proiect Symfony și putem lua și folosi orice componentă Symfony într-un proiect non-Symfony. Componentele Symfony pot servi ca exemple grozave de cod și sunt folosite în multe proiecte open source, cum ar fi Drupal, phpBB și Codeception. De fapt, Laravel în sine folosește nu mai puțin de 14 componente Symfony. Înțelegerea Symfony vă oferă astfel multe beneficii atunci când lucrați cu alte proiecte.
Instalații cadru
Ambele cadre vin cu programe de instalare și wrapper disponibile prin serverul web încorporat PHP.
Instalare Symfony
Instalarea Symfony este la fel de simplă ca următorul:
# Downloading Symfony installer sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony # Granting permissions to execute installer sudo chmod a+x /usr/local/bin/symfony # Creating new Symfony project symfony new symfony_project # Launching built-in server cd symfony_project/ && php bin/console server:start
Asta e! Instalarea dvs. Symfony este disponibilă pe adresa URL http://localhost:8000
.
Instalare Laravel
Procesul de instalare Laravel este aproape același și la fel de simplu ca cel pentru Symfony; singura diferență este că instalați instalatorul Laravel prin Composer:
# Downloading Laravel installer using Composer composer global require "laravel/installer" # Creating new Laravel project laravel new laravel_project # Launching built-in server cd laravel_project/ && php artisan serve
Acum puteți vizita http://localhost:8000
și puteți verifica instalarea Laravel.
Notă: Atât Laravel, cât și Symfony rulează de pe același port localhost (8000) în mod implicit, deci nu puteți rula aceste instanțe implicite simultan. Nu uitați să opriți serverul Symfony rulând php bin/console server:stop
înainte de a lansa serverul Laravel.
Despre instalarea cadrului
Acestea sunt exemple de instalare de bază. Pentru exemple de utilizare mai avansate, cum ar fi posibilitatea de a configura proiecte cu domenii locale sau de a rula mai multe proiecte simultan, ambele cadre oferă casete Vagrant:
- Laravel Homestead,
- Symfony Homestead.
Configurații de bază ale cadrului
Configurație de bază Symfony
Symfony folosește YAML ca sintaxă pentru specificarea configurației sale. Configurația implicită se află în fișierul app/config/config.yml
și arată ca următorul exemplu:
imports: - { resource: parameters.yml } - { resource: security.yml } - { resource: services.yml } framework: secret: '%secret%' router: { resource: '%kernel.root_dir%/config/routing.yml' } # ... # Twig Configuration twig: debug: '%kernel.debug%' strict_variables: '%kernel.debug%' # ...
Pentru a crea o configurație specifică mediului, creați fișierul app/config/config_ENV.yml
care conține parametrii de configurare de bază. Iată un exemplu de fișier config_dev.yml
pentru mediul de dezvoltare:
imports: - { resource: config.yml } # ... web_profiler: toolbar: true # ...
Acest exemplu activează instrumentul Symfony web_profiler
numai pentru mediul de dezvoltare. Acest instrument vă ajută să depanați și să vă profilați aplicația chiar în fereastra browserului.
În fișierele de configurare, puteți observa și construcții %secret%
. Ele ne permit să punem variabile specifice mediului în fișierul separat parameters.yml
. Acest fișier poate fi unic pe fiecare mașină și nu este stocat sub controlul versiunii. Pentru controlul versiunilor, avem un fișier special parameters.yml.dist
care este șablonul pentru fișierul parameters.yml
.
Iată un exemplu de fișier parameters.yml
:
parameters: database_host: 127.0.0.1 database_port: null database_name: symfony database_user: root database_password: null secret: f6b16aea89dc8e4bec811dea7c22d9f0e55593af
Configurație de bază Laravel
Configurația Laravel arată foarte diferită de cea a Symfony. Singurul lucru pe care îl au în comun este că ambii folosesc fișiere care nu sunt stocate sub controlul versiunii ( .env
în cazul Laravel) și un șablon pentru generarea acestui fișier ( .env.example
). Acest fișier are o listă de chei și valori, cum ar fi următorul exemplu:
APP_ENV=local APP_KEY=base64:Qm8mIaur5AygPDoOrU+IKecMLWgmcfOjKJItb7Im3Jk= APP_DEBUG=true APP_LOG_LEVEL=debug APP_URL=http://localhost
La fel ca și fișierul Symfony YAML, acesta pentru Laravel este, de asemenea, ușor de citit și arată curat. În plus, puteți crea fișierul .env.testing
care va fi folosit când rulați teste PHPUnit.
Configurația aplicației este stocată în fișiere .php
din directorul config
. Configurația de bază este stocată în fișierul app.php
și configurația specifică componentei este stocată în fișierele <component>.php
(de exemplu, cache.php
sau mail.php
). Iată un exemplu de fișier config/app.php
:
<?php return [ 'name' => 'Laravel', 'env' => env('APP_ENV', 'production'), 'debug' => env('APP_DEBUG', false), 'url' => env('APP_URL', 'http://localhost'), 'timezone' => 'UTC', 'locale' => 'en', // ... ];
Configurare cadru: Symfony vs. Laravel
Mecanismele de configurare a aplicației Symfony vă permit să creați fișiere diferite pentru medii diferite. În plus, vă împiedică să injectați logica PHP complexă în configurația YAML.
Cu toate acestea, s-ar putea să vă simțiți mai confortabil cu sintaxa implicită de configurare PHP pe care o folosește Laravel și nu trebuie să învățați sintaxa YAML.
Rutare și controler
În general, o aplicație web back-end are o responsabilitate principală: să citească fiecare cerere și să creeze un răspuns în funcție de conținutul cererii. Controlerul este o clasă responsabilă pentru transformarea cererii în răspuns prin apelarea metodelor de aplicație, în timp ce routerul este un mecanism care vă ajută să detectați ce clasă și metodă de controler ar trebui să executați pentru o anumită cerere.
Să creăm un controler care va afișa o pagină de postare de blog solicitată de pe ruta /posts/{id}
.
Rutare și controler în Laravel
Controlor
<?php namespace App\Http\Controllers; use App\Post; class BlogController extends Controller { /** * Show the blog post * @param int $id * @return \Illuminate\Http\Response */ public function show($id) { return view('post', ['post' => Post::findOrFail($id)]); } }
Router
Route::get('/posts/{id}', 'BlogController@show');
Am definit ruta pentru cererile GET
. Toate cererile cu URI care se potrivește cu /posts/{id}
vor executa metoda de show
a controlerului BlogController
și vor transmite id
-ul parametrului acelei metode. În controler, încercăm să găsim obiectul modelului POST
cu id
-ul transmis și să apelăm Laravel helper view()
pentru a reda pagina.
Rutare și controler în Symfony
În Symfony, exampleController
este puțin mai mare:
<?php namespace BlogBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class PostController extends Controller { /** * @Route("/posts/{id}") * @param int $id * @return \Symfony\Component\HttpFoundation\Response */ public function indexAction($id) { $repository = $this->getDoctrine()->getRepository('BlogBundle:Post'); $post = $repository->find($id); if ($post === null) { throw $this->createNotFoundException(); } return $this->render('BlogBundle:Post:show.html.twig', ['post'=>$post]); } }
Puteți vedea că am inclus deja @Route("/posts/{id}”)
în adnotare, așa că trebuie doar să includem controlerul în fișierul de configurare routing.yml
:
blog: resource: "@BlogBundle/Controller/" type: annotation prefix: /
Logica pas cu pas este aceeași ca în cazul Laravel.
Rutare și controler: Symfony vs. Laravel
În acest stadiu, ați putea crede că Laravel este mult mai drăguț decât Symfony. Acest lucru este adevărat, la început. Pare mult mai bine și mai ușor de început. Cu toate acestea, în aplicațiile din viața reală, nu ar trebui să apelați Doctrine de la controler. În schimb, ar trebui să apelați un serviciu care va încerca să găsească postarea sau să arunce HTTP 404 Exception .
Șabloane
Laravel este livrat cu un motor de șablon numit Blade, iar Symfony este livrat cu Twig. Ambele motoare de șablon implementează două caracteristici principale:
- Moștenirea șablonului
- Blocuri sau secțiuni
Ambele caracteristici vă permit să definiți un șablon de bază cu secțiuni care pot fi înlocuite și șabloane secundare care umple valorile acelor secțiuni.
Să luăm din nou exemplul unei pagini de postare pe blog.
Laravel Blade Template Engine
// base.blade.php <html> <head> <style></style> <title>@section('page-title') Welcome to blog! @show </title> </head> <body> <div class="container"> <h1>@yield('title')</h1> <div class="row"> @yield('content') </div> </div> </body> </html> // post.blade.php @extends('base') @section('page-title')Post {{ $post->title }} - read this and more in our blog.@endsection @section('title'){{ $post->title }}@endsection @section('content') {{ $post->content }} @endsection
Acum îi poți spune lui Laravel din Controlerul tău să redea șablonul post.blade.php
. Vă amintiți apelul nostru view('post', …)
din exemplul anterior al Controllerului? Nu trebuie să știți în codul dvs. că este moștenit de la un alt șablon. Totul este definit doar în șabloanele dvs., la nivel de vizualizare.
Motorul de șabloane Symfony Twig
// base.html.twig <html> <head> <style></style> <title>{% block page_title %} Welcome to blog! {% endblock %} </title> </head> <body> <div class="container"> <h1>{% block title %}{% endblock %}</h1> <div class="row"> {% block content %}{% endblock %} </div> </div> </body> </html> // show.html.twig {% extends '@Blog/base.html.twig' %} {% block page_title %}Post {{ post.title }} - read this and more in our blog.{% endblock %} {% block title %}{{ post.title }}{% endblock %} {% block content %} {{ post.content }} {% endblock %}
Șabloane: Symfony vs. Laravel
Din punct de vedere structural, șabloanele Blade și Twig sunt destul de asemănătoare. Ambele generează șabloane în codul PHP și funcționează rapid și ambele implementează structuri de control, cum ar fi instrucțiuni și bucle if
. Cea mai importantă caracteristică pe care o au ambele motoare este că scapă de ieșire în mod implicit, ceea ce ajută la prevenirea atacurilor XSS.
În afară de sintaxă, principala diferență este că Blade vă permite să injectați cod PHP direct în șabloane, iar Twig nu. În schimb, Twig vă permite să utilizați filtre.
De exemplu, dacă doriți să scrieți cu majuscule un șir, în Blade ați specifica următoarele:
{{ ucfirst('welcome friend') }}
În Twig, pe de altă parte, ați face următoarele:
{{ 'welcome friend'|capitalize }}
În Blade, este mai ușor să extinzi unele funcționalități, dar Twig nu permite niciun cod PHP direct în șabloane.
Injecție de dependență
Aplicațiile au o mulțime de servicii și componente diferite, cu diverse interdependențe. Trebuie să stocați cumva toate informațiile despre obiectele create și dependențele acestora.
Iată următoarea noastră componentă - Service Container. Este un obiect PHP care creează servicii solicitate și stochează informații despre obiectele create și dependențele acestora.
Să luăm în considerare următorul exemplu: Creați o clasă PostService
pentru a implementa o metodă care este responsabilă pentru crearea unei noi postări pe blog. Această clasă depinde de alte două servicii: PostRepository
, care este responsabil pentru stocarea informațiilor în baza de date și SubscriberNotifier
, care este responsabil pentru notificarea utilizatorilor abonați despre noua postare. Pentru ca acesta să funcționeze, trebuie să treceți aceste două servicii ca argumente constructoare ale PostService
sau, cu alte cuvinte, trebuie să injectați aceste dependențe.
Exemplu de injectare a dependenței Symfony
Mai întâi, să definim serviciile noastre de exemplu:
<?php // src/BlogBundle/Repository/PostRepository.php namespace BlogBundle\Repository; use BlogBundle\Entity\Post; use Doctrine\ORM\EntityRepository; class PostRepository extends EntityRepository { public function persist(Post $post) { // Perform save to db } }
<?php // src/BlogBundle/Service/SubscriberNotifier.php namespace BlogBundle\Service; use BlogBundle\Entity\Post; class SubscriberNotifier { public function notifyCreate(Post $post) { // Notify subscribers } }
<?php // src/BlogBundle/Service/PostService namespace BlogBundle\Service; use BlogBundle\Entity\Post; use BlogBundle\Repository\PostRepository; class PostService { /** @var PostRepository */ private $repository; /** @var SubscriberNotifier */ private $notifier; function __construct(PostRepository $repository, SubscriberNotifier $notifier) { $this->repository = $repository; $this->notifier = $notifier; } public function create(Post $post) { $this->repository->persist($post); $this->notifier->notifyCreate($post); } }
Urmează configurația de injectare a dependenței:
# src/BlogBundle/Resources/config/services.yml services: # Our main service blog.post_service: class: BlogBundle\Service\PostService arguments: ['@blog.post_repository', '@blog.subscriber_notifier'] # SubscriberNotifier service. It could also have its own dependencies, for example, mailer class. blog.subscriber_notifier: class: BlogBundle\Service\SubscriberNotifier # Repository. Don't dive deep into it's configuration, it is not a subject now blog.post_repository: class: BlogBundle\Repository\PostRepository factory: 'doctrine.orm.default_entity_manager:getRepository' arguments: - BlogBundle\Entity\Post
Acum puteți solicita serviciul de poștă oriunde în cod din obiectul Container de servicii. De exemplu, în controler ar putea fi ceva de genul acesta:
// Controller file. $post variable defined below $this->get('blog.post_service')->create($post);
Service Container este o componentă excelentă și vă ajută să vă construiți aplicația urmând principiile de proiectare SOLID.
Exemplu de injectare a dependenței Laravel
Este mult mai ușor să gestionezi dependențele în Laravel. Să luăm în considerare același exemplu:
<?php // app/Repository/PostRepository.php namespace App\Repository; use App\Post; class PostRepository { public function persist(Post $post) { // Perform save to db } }
<?php // app/Service/SubscriberNotifier.php namespace App\Service; use App\Post; class SubscriberNotifier { public function notifyCreate(Post $post) { // Notify subscribers } }
<?php // app/Service/PostService.php namespace App\Service; use App\Post; use App\Repository\PostRepository; class PostService { /** @var PostRepository */ private $repository; /** @var SubscriberNotifier */ private $notifier; public function __construct(PostRepository $repository, SubscriberNotifier $notifier) { $this->repository = $repository; $this->notifier = $notifier; } public function create(Post $post) { $this->repository->persist($post); $this->notifier->notifyCreate($post); } }
Aici vine frumusețea lui Laravel - nu trebuie să creați configurații de dependență . Laravel scanează automat dependențele pentru PostService
în tipurile de argumente ale constructorului și le rezolvă automat.

De asemenea, puteți utiliza injecția în metoda controlerului pentru a utiliza PostService
prin „indicarea tipului” în argumentele metodei:
<?php namespace App\Http\Controllers; use App\Post; use App\Service\PostService; class BlogController extends Controller { public function create(PostService $service) { $post = new Post(['title' => 'Title', 'content' => 'Content']); $service->create($post); return redirect('/posts/'.$post->id); } }
Injecție de dependență: Symfony vs. Laravel
Autodetecția lui Laravel funcționează excelent. Symfony are o capacitate similară numită „cablare automată”, care este dezactivată în mod implicit și poate fi activată prin adăugarea de autowire: true
configurației de dependență, dar necesită o anumită configurație. Calea Laravel este mai simplă.
Maparea relațională a obiectelor (ORM)
Pentru a lucra cu baze de date, ambele cadre vin cu caracteristici ORM (Object-Relational Mapping). ORM mapează înregistrările din baza de date la obiectele din cod. Pentru a face acest lucru, trebuie să creați modele pentru fiecare tip de înregistrare (sau fiecare tabel) din baza de date.
Symfony folosește un proiect terță parte Doctrine pentru a interacționa cu baza de date, în timp ce Laravel folosește propria bibliotecă Eloquent.
Eloquent ORM implementează modelul ActiveRecord pentru a lucra cu baza de date. În acest model, fiecare model este conștient de conexiunea la baza de date și poate interacționa cu aceasta. De exemplu, poate salva date în baza de date, poate actualiza sau șterge o înregistrare.
Doctrine implementează modelul Data Mapper, unde modelele nu știu nimic despre baza de date; sunt conștienți doar de datele în sine. Un strat special separat, EntityManager
, stochează toate informațiile despre interacțiunea dintre modele și baze de date și se ocupă de toate operațiunile.
Să luăm un exemplu pentru a înțelege diferența. Să presupunem că modelul dvs. are o cheie de id
, titlu, conținut și autor. Tabelul Postări stochează numai id
-ul autorului, așa că trebuie să creați o relație cu tabelul Users .
Doctrină
Să începem prin a defini modelele:
<?php // src/BlogBundle/Entity/User.php namespace BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * User * * @ORM\Table(name="user") * @ORM\Entity */ class User { /** * @var int * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM\Column(name="name", type="string", length=255) */ private $name; }
<?php // src/BlogBundle/Entity/Post.php namespace BlogBundle\Entity; use Doctrine\ORM\Mapping as ORM; /** * Post * * @ORM\Table(name="post") * @ORM\Entity(repositoryClass="BlogBundle\Repository\PostRepository") */ class Post { /** * @var int * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; /** * @var string * * @ORM\Column(name="title", type="string", length=255) */ protected $title; /** * @var string * * @ORM\Column(name="content", type="text") */ protected $content; /** * @var User * * @ORM\ManyToOne(targetEntity="BlogBundle\Entity\User") * @ORM\JoinColumn(name="author_id", referencedColumnName="id") */ protected $author;
Aici, am creat informații de mapare a modelului și acum putem folosi un ajutor pentru a genera stub-urile metodei:
php bin/console doctrine:generate:entities BlogBundle
În continuare, definim metodele post-depozitare:
<?php // src/BlobBundle/Repository/PostRepository.php namespace BlogBundle\Repository; use BlogBundle\Entity\Post; use Doctrine\ORM\EntityRepository; class PostRepository extends EntityRepository { /** * Store post to database * * @param Post $post */ public function persist(Post $post) { $this->getEntityManager()->persist($post); $this->getEntityManager()->flush(); } /** * Search posts with given author's name * * @param string $name * @return array */ public function findByAuthorName($name) { return $this->createQueryBuilder('posts') ->select('posts') ->join('posts.author', 'author') ->where('author.name = :name') ->setParameter('name', $name) ->getQuery() ->getResult(); } }
Acum puteți apela aceste metode din serviciu sau, de exemplu, din PostController
:
// To search for posts $posts = $this->getDoctrine()->getRepository('BlogBundle:Post')->findByAuthorName('Karim'); // To save new post in database $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post);
Elocvent
Modelul User este livrat cu Laravel și este definit implicit, așa că trebuie să definiți doar un model pentru Post .
<?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function author() { return $this->belongsTo('App\User', 'author_id'); } }
Asta e tot pentru modele. În Eloquent, nu trebuie să definiți proprietățile modelului, deoarece le construiește dinamic pe baza structurii tabelului bazei de date. Pentru a stoca o nouă postare $post
în baza de date, trebuie să efectuați acest apel (de la controlor, de exemplu):
$post->save();
Pentru a găsi toate postările unui autor cu un anumit nume, cea mai bună abordare ar fi să găsiți un utilizator cu numele său și să solicitați postările tuturor utilizatorilor:
$posts = Post::whereHas('author', function ($q) { $q->where('name', 'Karim'); })->get();
ORM: Symfony vs. Laravel
În ceea ce privește ORM, Eloquent pare mult mai prietenos pentru dezvoltatorii PHP și mai ușor de învățat decât Doctrine.
Dispatcher de evenimente vs. Middleware
Unul dintre cele mai importante lucruri de înțeles despre un cadru este ciclul său de viață.
Symfony și Event Dispatcher
Pentru a converti o solicitare într-un răspuns, Symfony folosește EventDispatcher. În consecință, declanșează diferite evenimente ale ciclului de viață și ascultători de evenimente speciale pentru a gestiona aceste evenimente. La început, trimite evenimentul kernel.request
care include informații despre solicitare. Principalul ascultător implicit al acestui eveniment este RouterListener
, care invocă componenta routerului pentru a găsi o regulă de rută potrivită pentru cererea curentă. După aceasta, alte evenimente sunt executate pas cu pas. Ascultătorii tipici ai evenimentelor sunt verificarea securității, verificarea jetonului CSRF și un proces de înregistrare. Dacă doriți să adăugați o anumită funcționalitate în ciclul de viață al cererii, trebuie să creați un EventListener
personalizat și să îl abonați la evenimentul necesar.
Laravel și Middleware
Laravel folosește o soluție diferită: middleware. Îmi place să compar middleware-ul cu o ceapă: aplicația dvs. are anumite straturi și o solicitare trece prin aceste straturi în drum spre controler și înapoi. Deci, dacă doriți să extindeți logica aplicației și să adăugați unele funcționalități în ciclul de viață al cererii, trebuie să adăugați un strat suplimentar la lista de middleware, iar Laravel îl va executa.
API-ul REST
Să încercăm să creăm un exemplu CRUD de bază pentru a gestiona o postare de blog:
- Creați -
POST /posts/
- Citiți -
GET /posts/{id}
- Actualizare -
PATCH /posts/{id}
- Șterge -
DELETE /posts/{id}
API-ul REST în Symfony
Symfony nu are o soluție ușoară pentru crearea rapidă a API-ului REST, dar are pachete excelente de la terți FOSRestBundle
și JMSSerializerBundle
.
Să luăm în considerare exemplul minim de lucru cu FOSRestBundle
și JMSSerializerBundle
. După ce le-ați instalat și le-ați activat în AppKernel
, puteți seta în configurația pachetului că veți folosi formatul JSON și că acesta nu trebuie să fie inclus în solicitările URL:
#app/config/config.yml fos_rest: routing_loader: default_format: json include_format: false
În configurația de rutare, ar trebui să specificați că acest controler va implementa o resursă REST:
#app/config/routing.yml blog: resource: BlogBundle\Controller\PostController type: rest
Ați implementat o metodă de persistență în depozit în exemplul anterior; acum trebuie să adăugați o metodă de ștergere:
// src/BlogBundle/Repository/PostRepository.php public function delete(Post $post) { $this->getEntityManager()->remove($post); $this->getEntityManager()->flush(); }
Apoi, trebuie să creați o clasă de formular pentru a accepta cererile de intrare și pentru a le mapa la model. Puteți face acest lucru folosind un ajutor CLI:
php bin/console doctrine:generate:form BlogBundle:Post
Veți primi un tip de formular generat cu următorul cod:
<?php // src/BlogBundle/Form/PostType.php namespace BlogBundle\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class PostType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('title')->add('content'); } /** * {@inheritdoc} */ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => 'BlogBundle\Entity\Post', 'csrf_protection' => false ]); } /** * {@inheritdoc} */ public function getBlockPrefix() { return 'post'; } }
Acum să implementăm controlerul nostru.
Notă: codul pe care vi-l voi arăta nu este perfect. Încalcă unele principii de proiectare, dar ar putea fi refactorizat cu ușurință. Scopul principal este de a vă arăta cum să implementați fiecare metodă, pas cu pas.
<?php // src/BlogBundle/Controller/PostController.php namespace BlogBundle\Controller; use BlogBundle\Entity\Post; use BlogBundle\Form\PostType; use FOS\RestBundle\Controller\FOSRestController; use FOS\RestBundle\View\View; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; class PostController extends FOSRestController { /** * @param $id * @return Response */ public function getPostAction($id) { $view = new View(); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post === null) { $view->setStatusCode(Response::HTTP_NOT_FOUND); } else { $view->setData(['post' => $post]); } return $this->handleView($view); } /** * @param Request $request * @return Response */ public function postPostAction(Request $request) { $view = new View(null, Response::HTTP_BAD_REQUEST); $post = new Post; $form = $this->createForm(PostType::class, $post, ['method' => $request->getMethod()]); $form->handleRequest($request); if ($form->isValid()) { $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post); $view->setStatusCode(Response::HTTP_CREATED); $postUrl = $this->generateUrl('get_post', ['id' => $post->getId()], UrlGeneratorInterface::ABSOLUTE_URL); $view->setHeader('Location', $postUrl); } else { $view->setData($form->getErrors()); } return $this->handleView($view); } /** * @param $id * @param Request $request * @return Response */ public function patchPostAction($id, Request $request) { $view = new View(null, Response::HTTP_BAD_REQUEST); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post === null) { $view->setStatusCode(Response::HTTP_NOT_FOUND); } else { $form = $this->createForm(PostType::class, $post, ['method' => $request->getMethod()]); $form->handleRequest($request); if ($form->isValid()) { $this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post); $view->setStatusCode(Response::HTTP_NO_CONTENT); $postUrl = $this->generateUrl('get_post', ['id' => $post->getId()], UrlGeneratorInterface::ABSOLUTE_URL); $view->setHeader('Location', $postUrl); } else { $view->setData($form->getErrors()); } } return $this->handleView($view); } /** * @param $id * @return Response */ public function deletePostAction($id) { $view = new View(null, Response::HTTP_NOT_FOUND); $post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id); if ($post !== null) { $this->getDoctrine()->getRepository('BlogBundle:Post')->delete($post); $view->setStatusCode(Response::HTTP_NO_CONTENT); } return $this->handleView($view); } }
Cu FOSRestBundle
, nu trebuie să declarați o rută pentru fiecare metodă; trebuie doar să urmați convenția cu numele metodelor controlerului, iar JMSSerializerBundle
va converti automat modelele în JSON.
API-ul REST în Laravel
În primul rând, trebuie să definiți rutele. Puteți face acest lucru în secțiunea API
a regulilor de rută pentru a dezactiva unele componente middleware implicite și a activa altele. Secțiunea API
se află în fișierul routes/api.php
.
<?php // routes/api.php Route::resource('/posts', 'BlogController');
În model, ar trebui să definiți proprietatea $fillable
pentru a trece variabile în metodele de creare și actualizare ale modelului:
<?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = ['title', 'content']; // …
Acum să definim controlerul:
<?php // app/Http/Controllers/BlogController.php namespace App\Http\Controllers; use App\Post; use Illuminate\Http\Request; use Illuminate\Http\Response; class BlogController extends Controller { public function show(Post $post) { return $post; } public function store(Request $request) { $post = Post::create($request->get('post')); return response(null, Response::HTTP_CREATED, ['Location'=>'/posts/'.$post->id]); } public function update(Post $post, Request $request) { $post->update($request->get('post')); return response(null, Response::HTTP_NO_CONTENT, ['Location'=>'/posts/'.$post->id]); } public function destroy(Post $post) { $post->delete(); return response(null, Response::HTTP_NO_CONTENT); } }
În Symfony, utilizați FosRestBundle
, care a inclus erorile în JSON. În Laravel, trebuie să o faci singur. Trebuie să actualizați metoda de randare în handlerul de excepții pentru a returna erori JSON pentru a aștepta solicitări JSON:
<?php // app/Exceptions/Handler.php namespace App\Exceptions; use Exception; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; class Handler extends ExceptionHandler { /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \Illuminate\Http\Response */ public function render($request, Exception $exception) { if ($request->expectsJson()) { $status = 400; if ($this->isHttpException($exception)) { $status = $exception->getStatusCode(); } elseif ($exception instanceof ModelNotFoundException) { $status = 404; } $response = ['message' => $exception->getMessage(), 'code' => $exception->getCode()]; return response()->json($response, $status); } return parent::render($request, $exception); } // ... }
API REST: Symfony vs. Laravel
După cum puteți vedea, pentru un API REST tipic, Laravel este mult mai simplu decât Symfony.
Alegerea câștigătorului: Symfony sau Laravel?
Nu există un câștigător clar între Laravel și Symfony, deoarece totul depinde de obiectivul tău final.
Laravel este o alegere mai bună dacă:
- Aceasta este prima ta experiență cu cadrul, deoarece este ușor de învățat și are o sintaxă mai simplă și materiale de învățare mai bune.
- Construiți un produs de pornire și vă verificați ipoteza, deoarece este bun pentru dezvoltarea rapidă a aplicațiilor, iar dezvoltatorii Laravel sunt ușor de găsit.
Symfony este cea mai bună opțiune dacă:
- Construiți o aplicație de întreprindere complexă, deoarece este foarte scalabilă, întreținută și bine structurată.
- Construiți o migrare a unui proiect mare pe termen lung, deoarece Symfony are planuri de lansare previzibile pentru următorii șase ani, așa că este mai puțin probabil să existe surprize.