Frameworki PHP: wybór między Symfony a Laravel

Opublikowany: 2022-03-11

Dziś, rozpoczynając nowy projekt, jedną z kluczowych decyzji jest wybór odpowiedniego frameworka. W dzisiejszych czasach trudno sobie wyobrazić zbudowanie od podstaw złożonej aplikacji internetowej bez niej.

Wiele popularnych języków do tworzenia stron internetowych ma swoje „domyślne” ramy, takie jak Ruby on Rails dla Ruby lub Django dla Pythona. Jednak PHP nie ma takiej pojedynczej wartości domyślnej i ma wiele popularnych opcji do wyboru.

Zgodnie z trendami Google i GitHub, najpopularniejsze frameworki PHP to Symfony z 13,7 tys. gwiazdek oraz Laravel z 29 tys. gwiazdek (w momencie pisania tego artykułu).

W tym artykule porównam te dwa frameworki i pokażę, jak zaimplementować w każdym z nich proste, codzienne funkcje. W ten sposób możesz porównać kod przykładów z życia wziętych obok siebie.

Ten artykuł zakłada silne umiejętności PHP i zrozumienie paradygmatu architektury MVC, ale nie jest wymagane wcześniejsze doświadczenie z Symfony lub Laravel.

Rywale

Laravel

Mówiąc o Laravelu, mamy na myśli wersję 4 i nowsze. Laravel 4 został wydany w 2013 roku i stanowił całkowite przepisanie frameworka. Funkcjonalność frameworka została rozdzielona na oddzielne komponenty, którymi zarządzał Composer, zamiast umieszczać wszystko w jednym ogromnym repozytorium kodu.

Laravel deklaruje się jako framework do szybkiego rozwoju z prostą i piękną składnią, która jest łatwa do nauczenia, czytania i utrzymania. Jest to najpopularniejszy framework w 2016 roku. Według trendów Google jest trzykrotnie bardziej popularny niż inne frameworki, a na GitHubie ma dwa razy więcej gwiazdek niż konkurenci.

Symfony

Symfony 2 zostało wydane w 2011 roku, ale nie należy go mylić z Symfony 1, który był zupełnie innym frameworkiem z różnymi podstawowymi zasadami. Fabien Potencier stworzył Symfony 2, a obecna wersja to 3.2, która jest przyrostową wersją Symfony 2. Dlatego często nazywa się je po prostu Symfony2/3.

Podobnie jak Laravel 4, Symfony 2 jest zaprojektowany jako zestaw odsprzężonych komponentów. Są tu dwie korzyści: możemy zastąpić dowolny komponent w projekcie Symfony i możemy wziąć i użyć dowolnego komponentu Symfony w projekcie innym niż Symfony. Komponenty Symfony mogą służyć jako świetne przykłady kodu i są używane w wielu projektach open source, takich jak Drupal, phpBB i Codeception. W rzeczywistości sam Laravel używa nie mniej niż 14 komponentów Symfony. Zrozumienie Symfony daje więc wiele korzyści podczas pracy z innymi projektami.

Instalacje Ramowe

Oba frameworki są dostarczane z instalatorami i wrapperami dostępnymi za pośrednictwem wbudowanego serwera WWW PHP.

Instalacja Symfony

Instalacja Symfony jest tak prosta, jak:

 # 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

Otóż ​​to! Twoja instalacja Symfony jest dostępna pod adresem URL http://localhost:8000 .

Instalacja Laravela

Proces instalacji Laravela jest prawie taki sam i tak prosty jak w przypadku Symfony; jedyną różnicą jest to, że instalujesz instalator Laravela przez 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

Możesz teraz odwiedzić http://localhost:8000 i sprawdzić swoją instalację Laravela.

Uwaga: Zarówno Laravel, jak i Symfony domyślnie korzystają z tego samego portu hosta lokalnego (8000), więc nie możesz mieć tych domyślnych instancji działających jednocześnie. Nie zapomnij zatrzymać serwera Symfony uruchamiając php bin/console server:stop przed uruchomieniem serwera Laravel.

O instalacji ramowej

To są przykłady podstawowej instalacji. W przypadku bardziej zaawansowanych przykładów użycia, takich jak możliwość konfigurowania projektów z domenami lokalnymi lub uruchamiania wielu projektów naraz, obie platformy zapewniają skrzynki Vagrant:

  • Zagroda Laravela,
  • Gospodarstwo Symfony.

Podstawowe konfiguracje platformy

Podstawowa konfiguracja Symfony

Symfony używa YAML jako składni do określenia swojej konfiguracji. Domyślna konfiguracja znajduje się w pliku app/config/config.yml i wygląda jak w poniższym przykładzie:

 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%' # ...

Aby utworzyć konfigurację specyficzną dla środowiska, utwórz plik app/config/config_ENV.yml zawierający podstawowe parametry konfiguracji. Oto przykład pliku config_dev.yml dla środowiska programistycznego:

 imports: - { resource: config.yml } # ... web_profiler: toolbar: true # ...

Ten przykład włącza narzędzie web_profiler Symfony tylko dla środowiska programistycznego. To narzędzie pomaga w debugowaniu i profilowaniu aplikacji bezpośrednio w oknie przeglądarki.

W plikach konfiguracyjnych można również zauważyć konstrukcje %secret% . Pozwalają nam na umieszczenie zmiennych specyficznych dla środowiska w osobnym pliku parameters.yml . Ten plik może być unikalny na każdym komputerze i nie jest przechowywany pod kontrolą wersji. Do kontroli wersji mamy specjalny plik parameters.yml.dist , który jest szablonem dla pliku parameters.yml .

Oto przykład pliku parameters.yml :

 parameters: database_host: 127.0.0.1 database_port: null database_name: symfony database_user: root database_password: null secret: f6b16aea89dc8e4bec811dea7c22d9f0e55593af

Podstawowa konfiguracja Laravela

Konfiguracja Laravela wygląda zupełnie inaczej niż w Symfony. Jedyną wspólną cechą jest to, że oboje używają plików, które nie są przechowywane pod kontrolą wersji ( .env w przypadku Laravela) i szablonu do generowania tego pliku ( .env.example ). Ten plik zawiera listę kluczy i wartości, jak w poniższym przykładzie:

 APP_ENV=local APP_KEY=base64:Qm8mIaur5AygPDoOrU+IKecMLWgmcfOjKJItb7Im3Jk= APP_DEBUG=true APP_LOG_LEVEL=debug APP_URL=http://localhost

Podobnie jak plik YAML Symfony, ten dla Laravela jest również czytelny dla człowieka i wygląda na czysty. Możesz dodatkowo stworzyć plik .env.testing , który będzie używany podczas uruchamiania testów PHPUnit.

Konfiguracja aplikacji jest przechowywana w plikach .php w katalogu config . Podstawowa konfiguracja jest przechowywana w pliku app.php , a konfiguracja komponentu jest przechowywana w plikach <component>.php (np. cache.php lub mail.php ). Oto przykład pliku 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', // ... ];

Konfiguracja frameworka: Symfony kontra Laravel

Mechanizmy konfiguracji aplikacji Symfony pozwalają na tworzenie różnych plików dla różnych środowisk. Dodatkowo zapobiega wstrzykiwaniu złożonej logiki PHP w konfiguracji YAML.

Jednak możesz czuć się bardziej komfortowo z domyślną składnią konfiguracji PHP, której używa Laravel i nie musisz uczyć się składni YAML.

Routing i kontroler

Ogólnie rzecz biorąc, aplikacja internetowa zaplecza ma jeden główny obowiązek: odczytywanie każdego żądania i tworzenie odpowiedzi w zależności od treści żądania. Kontroler to klasa odpowiedzialna za przekształcenie żądania na odpowiedź poprzez wywołanie metod aplikacji, natomiast router to mechanizm, który pomaga wykryć, jaką klasę i metodę kontrolera należy wykonać dla konkretnego żądania.

Stwórzmy kontroler, który będzie wyświetlał stronę posta na blogu żądaną z trasy /posts/{id} .

Routing i kontroler w Laravel

Kontroler

 <?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');

Zdefiniowaliśmy trasę dla żądań GET . Wszystkie żądania z identyfikatorem URI pasującym do /posts/{id} wykonają polecenia kontrolera BlogController show the method i przekażą id parametru do tej metody. W kontrolerze próbujemy znaleźć obiekt modelu POST z przekazanym id i wywołujemy helper Laravel view() w celu wyrenderowania strony.

Routing i kontroler w Symfony

Przykładowy kontroler w exampleController jest nieco większy:

 <?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]); } }

Jak widać, w adnotacji zawarliśmy już @Route("/posts/{id}”) , więc wystarczy dołączyć kontroler do pliku konfiguracyjnego routing.yml :

 blog: resource: "@BlogBundle/Controller/" type: annotation prefix: /

Logika krok po kroku jest taka sama jak w przypadku Laravela.

Routing i kontroler: Symfony kontra Laravel

Na tym etapie możesz pomyśleć, że Laravel jest znacznie milszy niż Symfony. To prawda na początku. Wygląda o wiele lepiej i łatwiej zacząć. Jednak w rzeczywistych aplikacjach nie powinieneś wywoływać Doctrine z kontrolera. Zamiast tego należy wywołać usługę, która spróbuje znaleźć wpis lub zgłosić wyjątek HTTP 404 .

Szablony

Laravel jest dostarczany z silnikiem szablonów o nazwie Blade, a Symfony jest dostarczany z Twig. Oba aparaty szablonów implementują dwie główne funkcje:

  1. Dziedziczenie szablonów
  2. Bloki lub sekcje

Obie funkcje umożliwiają zdefiniowanie podstawowego szablonu z nadpisanymi sekcjami i szablonami podrzędnymi, które wypełniają wartości tych sekcji.

Rozważmy ponownie przykład strony z wpisami na blogu.

Silnik szablonów ostrzy Laravel

 // 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

Teraz możesz powiedzieć Laravelowi w kontrolerze, aby renderował szablon post.blade.php . Czy pamiętasz nasze wywołanie view('post', …) w poprzednim przykładzie Kontrolera? Nie musisz wiedzieć w swoim kodzie, że jest on dziedziczony z innego szablonu. To wszystko jest zdefiniowane tylko w Twoich szablonach, na poziomie widoku.

Silnik szablonów 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 %}

Szablony: Symfony kontra Laravel

Strukturalnie szablony Blade i Twig są dość podobne. Oba generują szablony w kodzie PHP i działają szybko, a także implementują struktury kontrolne, takie jak instrukcje if i pętle. Najważniejszą cechą obu silników jest to, że domyślnie unikają danych wyjściowych, co pomaga zapobiegać atakom XSS.

Oprócz składni, główna różnica polega na tym, że Blade pozwala na wstrzykiwanie kodu PHP bezpośrednio do szablonów, a Twig nie. Zamiast tego Twig umożliwia korzystanie z filtrów.

Na przykład, jeśli chcesz pisać wielką literą, w Blade określisz:

 {{ ucfirst('welcome friend') }}

Z drugiej strony w Twig wykonałbyś następujące czynności:

 {{ 'welcome friend'|capitalize }}

W Blade łatwiej jest rozszerzyć niektóre funkcjonalności, ale Twig nie pozwala na bezpośredni kod PHP w szablonach.

Wstrzykiwanie zależności

Aplikacje mają wiele różnych usług i komponentów o różnych współzależnościach. Musisz jakoś przechowywać wszystkie informacje o tworzonych obiektach i ich zależnościach.

Oto nasz kolejny komponent - Service Container. Jest to obiekt PHP, który tworzy żądane usługi i przechowuje informacje o utworzonych obiektach i ich zależnościach.

Rozważmy następujący przykład: Tworzysz klasę PostService , aby zaimplementować metodę odpowiedzialną za utworzenie nowego wpisu na blogu. Ta klasa zależy od dwóch innych usług: PostRepository , która odpowiada za przechowywanie informacji w bazie danych oraz SubscriberNotifier , która odpowiada za powiadamianie subskrybowanych użytkowników o nowym poście. Aby to zadziałało, musisz przekazać te dwie usługi jako argumenty konstruktora PostService lub innymi słowy, musisz wstrzyknąć te zależności.

Przykład wstrzykiwania zależności Symfony

Najpierw zdefiniujmy nasze przykładowe usługi:

 <?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); } }

Dalej jest konfiguracja wstrzykiwania zależności:

 # 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

Teraz możesz zażądać usługi pocztowej w dowolnym miejscu kodu z obiektu kontenera usług. Na przykład w kontrolerze może to być coś takiego:

 // Controller file. $post variable defined below $this->get('blog.post_service')->create($post);

Kontener usług to świetny składnik, który pomaga w tworzeniu aplikacji zgodnie z zasadami projektowania SOLID.

Powiązane: True Dependency Injection z komponentami Symfony

Przykład wstrzykiwania zależności Laravel

Znacznie łatwiej jest zarządzać zależnościami w Laravelu. Rozważmy ten sam przykład:

 <?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); } }

I tu pojawia się piękno Laravela — nie trzeba tworzyć konfiguracji zależności . Laravel automatycznie skanuje zależności dla PostService w jego typach argumentów konstruktora i automatycznie je rozwiązuje.

Możesz również użyć wstrzykiwania w metodzie kontrolera, aby użyć PostService przez „podpowiedź” w argumentach metody:

 <?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); } }

Zastrzyk zależności: Symfony kontra Laravel

Autodetekcja Laravela działa świetnie. Symfony ma podobną funkcję zwaną „autowire”, która jest domyślnie wyłączona i może być włączona przez dodanie autowire: true do konfiguracji zależności, ale wymaga pewnej konfiguracji. Sposób Laravela jest prostszy.

Mapowanie relacyjne obiektów (ORM)

Aby pracować z bazami danych, obie platformy są wyposażone w funkcje mapowania obiektowo-relacyjnego (ORM). ORM mapuje rekordy z bazy danych na obiekty w kodzie. Aby to zrobić, musisz utworzyć modele dla każdego typu rekordu (lub każdej tabeli) w swojej bazie danych.

Symfony korzysta z zewnętrznego projektu Doctrine do interakcji z bazą danych, podczas gdy Laravel korzysta z własnej biblioteki Eloquent.

Eloquent ORM implementuje wzorzec ActiveRecord do pracy z bazą danych. W tym wzorcu każdy model jest świadomy połączenia z bazą danych i może z nią wchodzić w interakcje. Na przykład może zapisywać dane w bazie danych, aktualizować lub usuwać rekord.

Doctrine implementuje wzorzec Data Mapper, w którym modele nie wiedzą nic o bazie danych; znają tylko same dane. Specjalna osobna warstwa EntityManager przechowuje wszystkie informacje o interakcji między modelami a bazami danych i obsługuje wszystkie operacje.

Weźmy przykład, aby zrozumieć różnicę. Załóżmy, że Twój model ma klucz id podstawowego, tytuł, treść i autora. Tabela Posts przechowuje tylko id autora , więc musisz utworzyć relację z tabelą Users .

Doktryna

Zacznijmy od zdefiniowania modeli:

 <?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;

Tutaj stworzyliśmy informacje o mapowaniu modelu i możemy teraz użyć helpera do wygenerowania kodów pośredniczących metody:

 php bin/console doctrine:generate:entities BlogBundle

Następnie definiujemy metody repozytorium postów:

 <?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(); } }

Teraz możesz wywołać te metody z usługi lub na przykład z 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);

Wymowny

Model użytkownika jest dostarczany z Laravel i jest zdefiniowany domyślnie, więc wystarczy zdefiniować tylko jeden model dla 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'); } }

To wszystko dla modeli. W Eloquent nie musisz definiować właściwości modelu, ponieważ buduje je dynamicznie w oparciu o strukturę tabeli bazy danych. Aby zapisać nowy post $post w bazie danych, musisz wykonać to wywołanie (na przykład z kontrolera):

 $post->save();

Aby znaleźć wszystkie posty autora o podanym nazwisku, najlepszym rozwiązaniem byłoby znalezienie użytkownika o jego nazwie i zażądanie postów wszystkich użytkowników:

 $posts = Post::whereHas('author', function ($q) { $q->where('name', 'Karim'); })->get();

ORM: Symfony kontra Laravel

Jeśli chodzi o ORM, Eloquent wygląda o wiele bardziej przyjaźnie dla programistów PHP i łatwiejszy do nauczenia niż Doctrine.

Dyspozytor zdarzeń a oprogramowanie pośredniczące

Cykl życia Symfony kontra Laravel

Jedną z najważniejszych rzeczy, które należy zrozumieć o frameworku, jest jego cykl życia.

Symfony i Event Dispatcher

Aby przekształcić żądanie w odpowiedź, Symfony używa EventDispatcher. W konsekwencji uruchamia różne zdarzenia cyklu życia i specjalne detektory zdarzeń do obsługi tych zdarzeń. Na początku wywołuje zdarzenie kernel.request , które zawiera informacje o żądaniu. Głównym domyślnym odbiornikiem tego zdarzenia jest RouterListener , który wywołuje komponent routera w celu znalezienia odpowiedniej reguły trasy dla bieżącego żądania. Następnie krok po kroku wykonywane są inne zdarzenia. Typowe detektory zdarzeń to kontrola bezpieczeństwa, weryfikacja tokenu CSRF i proces rejestrowania. Jeśli chcesz dodać jakąś funkcjonalność w cyklu życia żądania, musisz utworzyć niestandardowy EventListener i zasubskrybować go do niezbędnego zdarzenia.

Laravel i oprogramowanie pośredniczące

Laravel korzysta z innego rozwiązania: oprogramowania pośredniczącego. Lubię porównywać oprogramowanie pośredniczące do cebuli: Twoja aplikacja ma określone warstwy i żądanie przechodzi przez te warstwy w drodze do kontrolera iz powrotem. Tak więc, jeśli chcesz rozszerzyć logikę aplikacji i dodać jakąś funkcjonalność w cyklu życia żądania, musisz dodać dodatkową warstwę do swojej listy oprogramowania pośredniczącego, a Laravel ją wykona.

REST API

Spróbujmy stworzyć prosty przykład CRUD do zarządzania postem na blogu:

  • Utwórz - POST /posts/
  • Przeczytaj - GET /posts/{id}
  • Aktualizacja - PATCH /posts/{id}
  • Usuń - DELETE /posts/{id}

REST API w Symfony

Symfony nie ma łatwego, gotowego do użycia rozwiązania do szybkiego tworzenia interfejsu API REST, ale ma świetne pakiety innych firm FOSRestBundle i JMSSerializerBundle .

Rozważmy minimalny działający przykład z FOSRestBundle i JMSSerializerBundle . Po ich zainstalowaniu i włączeniu w AppKernel , możesz ustawić w konfiguracji pakietu, że będziesz używać formatu JSON i że nie musi to być uwzględnione w żądaniach URL:

 #app/config/config.yml fos_rest: routing_loader: default_format: json include_format: false

W konfiguracji routingu należy określić, że ten kontroler zaimplementuje zasób REST:

 #app/config/routing.yml blog: resource: BlogBundle\Controller\PostController type: rest

W poprzednim przykładzie zaimplementowano metodę utrwalania w repozytorium; teraz musisz dodać metodę usuwania:

 // src/BlogBundle/Repository/PostRepository.php public function delete(Post $post) { $this->getEntityManager()->remove($post); $this->getEntityManager()->flush(); }

Następnie musisz utworzyć klasę formularza, aby akceptować żądania danych wejściowych i mapować je do modelu. Możesz to zrobić za pomocą helpera CLI:

 php bin/console doctrine:generate:form BlogBundle:Post

Otrzymasz wygenerowany typ formularza z następującym kodem:

 <?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'; } }

Teraz zaimplementujmy nasz kontroler.

Uwaga: kod, który ci pokażę, nie jest doskonały. Narusza to niektóre zasady projektowania, ale można je łatwo zmienić. Głównym celem jest pokazanie, jak krok po kroku wdrożyć każdą metodę.

 <?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); } }

Dzięki FOSRestBundle nie musisz deklarować trasy dla każdej metody; po prostu postępuj zgodnie z konwencją z nazwami metod kontrolera, a JMSSerializerBundle automatycznie przekonwertuje Twoje modele na JSON.

REST API w Laravel

Najpierw musisz zdefiniować trasy. Możesz to zrobić w sekcji API reguł tras, aby wyłączyć niektóre domyślne składniki oprogramowania pośredniego i włączyć inne. Sekcja API znajduje się w pliku routes/api.php .

 <?php // routes/api.php Route::resource('/posts', 'BlogController');

W modelu należy zdefiniować właściwość $fillable , aby przekazywać zmienne w metodach tworzenia i aktualizacji modelu:

 <?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = ['title', 'content']; // …

Teraz zdefiniujmy kontroler:

 <?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); } }

W Symfony używasz FosRestBundle , który zawiera błędy w JSON. W Laravel musisz zrobić to sam. Musisz zaktualizować metodę render w procedurze obsługi wyjątków, aby zwracała błędy JSON związane z oczekiwaniem żądań 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); } // ... }

REST API: Symfony kontra Laravel

Jak widać, dla typowego API REST, Laravel jest znacznie prostszy niż Symfony.

Wybór zwycięzcy: Symfony czy Laravel?

Nie ma wyraźnego zwycięzcy między Laravel i Symfony, ponieważ wszystko zależy od ostatecznego celu.

Laravel jest lepszym wyborem, jeśli:

  • To Twoje pierwsze doświadczenie z frameworkiem, ponieważ jest łatwy do nauczenia, ma prostszą składnię i lepsze materiały do ​​nauki.
  • Budujesz produkt startowy i sprawdzasz swoją hipotezę, ponieważ jest to dobre dla szybkiego tworzenia aplikacji, a programiści Laravel są łatwi do znalezienia.

Symfony to najlepsza opcja, jeśli:

  • Budujesz złożoną aplikację korporacyjną, ponieważ jest bardzo skalowalna, łatwa w utrzymaniu i dobrze ustrukturyzowana.
  • Budujesz migrację dużego, długoterminowego projektu, ponieważ Symfony ma przewidywalne plany wydawnicze na najbliższe sześć lat, więc jest mniej prawdopodobne, że będą jakieś niespodzianki.