PHP 框架:在 Symfony 和 Laravel 之间进行选择
已发表: 2022-03-11今天,当开始一个新项目时,关键决定之一就是选择正确的框架。 现在很难想象没有人从头开始构建一个复杂的 Web 应用程序。
许多用于 Web 开发的流行语言都有其“默认”框架,例如用于 Ruby 的 Ruby on Rails 或用于 Python 的 Django。 然而,PHP 没有这样的单一默认值,并且有多个流行的选项可供选择。
根据 Google 趋势和 GitHub,最流行的 PHP 框架是拥有 13.7k 星的 Symfony 和拥有 29k 星的 Laravel(在撰写本文时)。
在本文中,我将比较这两个框架,并向您展示如何使用每个框架实现简单的日常功能。 这样,您可以并排比较实际示例的代码。
本文假定您具备强大的 PHP 技能和对 MVC 架构范式的理解,但不需要之前使用过 Symfony 或 Laravel 的经验。
竞争者
拉拉维尔
当谈到 Laravel 时,我们指的是 Laravel 版本 4 及更高版本。 Laravel 4 于 2013 年发布,代表了对框架的完全重写。 框架的功能被分离成单独的组件,由 Composer 管理,而不是所有东西都在一个巨大的代码存储库中。
Laravel 宣称自己是一个快速开发的框架,其语法简单漂亮,易于学习、阅读和维护。 它是 2016 年最流行的框架。根据 Google 趋势,它的受欢迎程度是其他框架的 3 倍,在 GitHub 上,它的 star 是竞争对手的 2 倍。
Symfony
Symfony 2 于 2011 年发布,但千万不要将它与 Symfony 1 混淆,后者是一个完全不同的框架,具有不同的基本原理。 Fabien Potencier 创建了 Symfony 2,当前版本是 3.2,是 Symfony 2 的增量版本。因此,它们通常被简称为 Symfony2/3。
与 Laravel 4 一样,Symfony 2 被设计为一组解耦的组件。 这里有两个好处:我们可以替换 Symfony 项目中的任何组件,并且我们可以在非 Symfony 项目中获取和使用任何 Symfony 组件。 Symfony 组件可以作为很好的代码示例,它们被用于许多开源项目,例如 Drupal、phpBB 和 Codeception。 事实上,Laravel 本身使用了不下 14 个 Symfony 组件。 因此,理解 Symfony 会给您在处理其他项目时带来很多好处。
框架安装
这两个框架都带有可通过 PHP 内置 Web 服务器获得的安装程序和包装程序。
Symfony 安装
Symfony 的安装非常简单,如下所示:
# 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
而已! 您的 Symfony 安装在 URL http://localhost:8000
上可用。
Laravel 安装
Laravel 的安装过程几乎和 Symfony 一样简单; 唯一的区别是你通过 Composer 安装 Laravel 的安装程序:
# 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
你现在可以访问http://localhost:8000
并检查你的 Laravel 安装。
注意: Laravel 和 Symfony 默认运行在同一个 localhost 端口(8000),所以你不能让这些默认实例同时运行。 在启动 Laravel 服务器之前,不要忘记通过运行php bin/console server:stop
来停止 Symfony 服务器。
关于框架安装
这些是基本安装的示例。 对于更高级的使用示例,例如能够使用本地域配置项目或一次运行多个项目,两个框架都提供了 Vagrant 框:
- Laravel宅基地,
- Symfony 家园。
基本框架配置
Symfony 基本配置
Symfony 使用 YAML 作为指定其配置的语法。 默认配置位于app/config/config.yml
文件中,类似于以下示例:
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%' # ...
要创建特定于环境的配置,请创建包含基本配置参数的文件app/config/config_ENV.yml
。 以下是开发环境的config_dev.yml
文件示例:
imports: - { resource: config.yml } # ... web_profiler: toolbar: true # ...
这个例子只为开发环境打开web_profiler
Symfony 工具。 此工具可帮助您直接在浏览器窗口中调试和分析您的应用程序。
在配置文件中,您还可以注意到%secret%
结构。 它们允许我们将特定于环境的变量放在单独的parameters.yml
文件中。 此文件在每台机器上可能是唯一的,并且不受版本控制存储。 对于版本控制,我们有一个特殊的parameters.yml.dist
文件,它是parameters.yml
文件的模板。
以下是parameters.yml
文件的示例:
parameters: database_host: 127.0.0.1 database_port: null database_name: symfony database_user: root database_password: null secret: f6b16aea89dc8e4bec811dea7c22d9f0e55593af
Laravel 基本配置
Laravel 配置看起来与 Symfony 的配置非常不同。 它们唯一的共同点是它们都使用未存储在版本控制下的文件(在.env
案例中为 .env )和生成此文件的模板( .env.example
)。 该文件有一个键和值列表,如下例所示:
APP_ENV=local APP_KEY=base64:Qm8mIaur5AygPDoOrU+IKecMLWgmcfOjKJItb7Im3Jk= APP_DEBUG=true APP_LOG_LEVEL=debug APP_URL=http://localhost
就像 Symfony YAML 文件一样,这个用于 Laravel 的文件也是人类可读的并且看起来很干净。 您还可以创建运行 PHPUnit 测试时使用的.env.testing
文件。
应用程序配置存储在config
目录中的.php
文件中。 基本配置存储在app.php
文件中,特定于组件的配置存储在<component>.php
文件中(例如cache.php
或mail.php
)。 下面是一个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', // ... ];
框架配置:Symfony 与 Laravel
Symfony 的应用程序配置机制允许您为不同的环境创建不同的文件。 此外,它还可以防止您在 YAML 配置中注入复杂的 PHP 逻辑。
但是,您可能对 Laravel 使用的默认 PHP 配置语法感觉更舒服,而且您不必学习 YAML 语法。
路由和控制器
通常,后端 Web 应用程序有一个主要职责:读取每个请求并根据请求的内容创建响应。 控制器是一个类,负责通过调用应用程序方法将请求转换为响应,而路由器是一种机制,可帮助您检测应为特定请求执行哪个控制器类和方法。
让我们创建一个控制器来显示从/posts/{id}
路由请求的博客文章页面。
Laravel 中的路由和控制器
控制器
<?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)]); } }
路由器
Route::get('/posts/{id}', 'BlogController@show');
我们已经为GET
请求定义了路由。 URI 与/posts/{id}
匹配的所有请求都将执行BlogController
控制器的show
方法,并将参数id
传递给该方法。 在控制器中,我们试图通过传递的id
找到模型POST
的对象,并调用 Laravel 助手view()
来渲染页面。
Symfony 中的路由和控制器
在 Symfony 中, exampleController
稍微大一点:
<?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]); } }
你可以看到我们已经在注解中包含@Route("/posts/{id}”)
,所以我们只需要在routing.yml
配置文件中包含控制器:
blog: resource: "@BlogBundle/Controller/" type: annotation prefix: /
一步一步的逻辑与 Laravel 案例中的相同。
路由和控制器:Symfony 与 Laravel
在这个阶段,你可能会认为 Laravel 比 Symfony 好得多。 这是真的,在一开始。 它看起来更好,更容易开始。 但是,在现实生活中的应用程序中,您不应该从控制器调用 Doctrine。 相反,您应该调用将尝试查找帖子或抛出HTTP 404 Exception的服务。
模板
Laravel 附带了一个名为 Blade 的模板引擎,而 Symfony 附带了 Twig。 两个模板引擎都实现了两个主要功能:
- 模板继承
- 块或部分
这两个功能都允许您定义一个基本模板,其中包含可覆盖的部分和填充这些部分的值的子模板。
让我们再次考虑博客文章页面的示例。
Laravel Blade 模板引擎
// 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
现在你可以在 Controller 中告诉 Laravel 渲染模板post.blade.php
。 你还记得前面控制器示例中的view('post', …)
调用吗? 您无需在代码中知道它是从其他模板继承的。 它只在您的模板中,在视图级别上定义。
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 %}
模板:Symfony 与 Laravel
在结构上,Blade 和 Twig 模板非常相似。 两者都将模板生成到 PHP 代码中并且工作速度很快,并且都实现了控制结构,例如if
语句和循环。 这两个引擎最重要的特性是它们默认转义输出,这有助于防止 XSS 攻击。
除了语法之外,主要区别在于 Blade 允许您将 PHP 代码直接注入到模板中,而 Twig 不允许。 相反,Twig 允许您使用过滤器。
例如,如果你想大写一个字符串,在 Blade 中你可以指定以下内容:
{{ ucfirst('welcome friend') }}
另一方面,在 Twig 中,您将执行以下操作:
{{ 'welcome friend'|capitalize }}
在 Blade 中,扩展某些功能更容易,但 Twig 不允许在模板中使用任何直接的 PHP 代码。
依赖注入
应用程序有许多不同的服务和组件,具有各种相互依赖关系。 您需要以某种方式存储有关已创建对象及其依赖项的所有信息。
这是我们的下一个组件——服务容器。 它是一个 PHP 对象,用于创建请求的服务并存储有关创建的对象及其依赖项的信息。
让我们考虑以下示例:您正在创建一个PostService
类来实现负责创建新博客文章的方法。 这个类依赖于另外两个服务: PostRepository
,它负责在数据库中存储信息,以及SubscriberNotifier
,它负责通知订阅用户新的帖子。 要使其工作,您需要将这两个服务作为PostService
的构造函数参数传递,或者换句话说,您需要注入这些依赖项。
Symfony 依赖注入示例
首先,让我们定义我们的示例服务:
<?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); } }
接下来是依赖注入配置:
# 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
现在,您可以在您的服务容器对象的代码中的任何位置请求您的后期服务。 例如,在控制器中它可能是这样的:
// Controller file. $post variable defined below $this->get('blog.post_service')->create($post);
Service Container 是一个很棒的组件,它有助于按照 SOLID 设计原则构建您的应用程序。
Laravel 依赖注入示例
在 Laravel 中管理依赖项要容易得多。 让我们考虑同样的例子:
<?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); } }
这就是 Laravel 的美妙之处——你不需要创建依赖配置。 PostService
在其构造函数参数类型中自动扫描 PostService 的依赖关系并自动解析它们。
您还可以在控制器方法中使用注入来使用PostService
,方法是在方法参数中“类型提示”它:
<?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); } }
依赖注入:Symfony 与 Laravel
Laravel 的自动检测效果很好。 Symfony 有一个类似的功能,称为“autowire”,默认情况下是关闭的,可以通过添加autowire: true
到你的依赖配置来打开,但它需要一些配置。 Laravel 的方式更简单。

对象关系映射 (ORM)
为了使用数据库,这两个框架都带有对象关系映射 (ORM) 功能。 ORM 将数据库中的记录映射到代码中的对象。 为此,您必须为数据库中的每种记录类型(或每个表)创建模型。
Symfony 使用第三方项目 Doctrine 与数据库交互,而 Laravel 使用自己的库 Eloquent。
Eloquent ORM 实现了 ActiveRecord 模式来处理数据库。 在这种模式中,每个模型都知道与数据库的连接并可以与之交互。 例如,它可以将数据保存到数据库、更新或删除记录。
Doctrine 实现了 Data Mapper 模式,其中模型对数据库一无所知; 他们只知道数据本身。 一个特殊的独立层EntityManager
存储有关模型和数据库之间交互的所有信息,并处理所有操作。
让我们举个例子来理解其中的区别。 假设您的模型有一个主id
键、标题、内容和作者。 Posts表仅存储作者id
,因此您需要创建与Users表的关系。
教义
让我们从定义模型开始:
<?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;
在这里,我们创建了模型映射信息,现在可以使用帮助程序来生成方法存根:
php bin/console doctrine:generate:entities BlogBundle
接下来,我们定义后存储库方法:
<?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(); } }
现在您可以从服务或例如从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);
雄辩
User模型是 Laravel 自带的,它是默认定义的,所以你只需要为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'); } }
这就是模型的全部内容。 在 Eloquent 中,您不必定义模型属性,因为它会根据数据库表结构动态构建它们。 要将新帖子$post
存储到数据库中,您需要进行此调用(例如,从控制器):
$post->save();
要查找具有给定名称的作者的所有帖子,最好的方法是找到具有其名称的用户并请求所有用户的帖子:
$posts = Post::whereHas('author', function ($q) { $q->where('name', 'Karim'); })->get();
ORM:Symfony 与 Laravel
关于 ORM,Eloquent 看起来对 PHP 开发人员更友好,也比 Doctrine 更容易学习。
事件调度程序与中间件
了解框架的最重要的事情之一是它的生命周期。
Symfony 和事件调度器
为了将请求转换为响应,Symfony 使用 EventDispatcher。 因此,它会触发不同的生命周期事件和特殊事件侦听器来处理这些事件。 一开始,它会分派包含请求信息的kernel.request
事件。 此事件的主要默认侦听器是RouterListener
,它调用路由器组件为当前请求找到合适的路由规则。 在此之后,将逐步执行其他事件。 典型的事件侦听器是安全检查、CSRF 令牌验证和日志记录过程。 如果你想在请求生命周期中添加一些功能,你需要创建一个自定义的EventListener
并订阅必要的事件。
Laravel 和中间件
Laravel 使用了不同的解决方案:中间件。 我喜欢将中间件比作洋葱:您的应用程序具有某些层,并且请求在到达控制器并返回的途中通过这些层。 所以,如果你想扩展你的应用程序逻辑并在请求生命周期中添加一些功能,你需要在你的中间件列表中添加一个额外的层,Laravel 会执行它。
REST API
让我们尝试创建一个基本的 CRUD 示例来管理博客文章:
- 创建 -
POST /posts/
- 阅读 -
GET /posts/{id}
- 更新 -
PATCH /posts/{id}
- 删除 -
DELETE /posts/{id}
Symfony 中的 REST API
Symfony 没有用于快速创建 REST API 的简单的开箱即用解决方案,但它有很棒的第三方包FOSRestBundle
和JMSSerializerBundle
。
让我们考虑使用FOSRestBundle
和JMSSerializerBundle
的最小工作示例。 在您安装它们并在AppKernel
中打开它们之后,您可以在捆绑配置中设置您将使用 JSON 格式并且这不必包含在 URL 请求中:
#app/config/config.yml fos_rest: routing_loader: default_format: json include_format: false
在路由配置中,您应该指定此控制器将实现 REST 资源:
#app/config/routing.yml blog: resource: BlogBundle\Controller\PostController type: rest
在前面的示例中,您在存储库中实现了一个 persist 方法; 现在您需要添加一个删除方法:
// src/BlogBundle/Repository/PostRepository.php public function delete(Post $post) { $this->getEntityManager()->remove($post); $this->getEntityManager()->flush(); }
接下来,您需要创建一个表单类来接受输入请求并将它们映射到模型。 您可以使用 CLI 帮助程序来做到这一点:
php bin/console doctrine:generate:form BlogBundle:Post
您将收到带有以下代码的生成表单类型:
<?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'; } }
现在让我们实现我们的控制器。
注意:我将向您展示的代码并不完美。 它违反了一些设计原则,但很容易重构。 主要目的是向您展示如何逐步实现每种方法。
<?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); } }
使用FOSRestBundle
,您不需要为每个方法声明一个路由; 只需遵循控制器方法名称的约定, JMSSerializerBundle
就会自动将您的模型转换为 JSON。
Laravel 中的 REST API
首先,您需要定义路线。 您可以在路由规则的API
部分执行此操作,以关闭一些默认中间件组件并打开其他组件。 API
部分位于routes/api.php
文件中。
<?php // routes/api.php Route::resource('/posts', 'BlogController');
在模型中,您应该定义$fillable
属性以在模型的创建和更新方法中传递变量:
<?php // app/Post.php namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { protected $fillable = ['title', 'content']; // …
现在让我们定义控制器:
<?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); } }
在 Symfony 中,您使用的是FosRestBundle
,它将错误包装在 JSON 中。 在 Laravel 中,你需要自己做。 您需要更新 Exception 处理程序中的 render 方法以返回预期 JSON 请求的 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 与 Laravel
如您所见,对于典型的 REST API,Laravel 比 Symfony 简单得多。
选择赢家:Symfony 还是 Laravel?
Laravel 和 Symfony 之间没有明显的赢家,因为一切都取决于你的最终目标。
如果满足以下条件,Laravel 是更好的选择:
- 这是您对该框架的第一次体验,因为它易于学习,并且具有更简单的语法和更好的学习材料。
- 你正在构建一个启动产品并检查你的假设,因为它有利于快速应用程序开发,并且很容易找到 Laravel 开发人员。
如果满足以下条件,Symfony 是最佳选择:
- 您正在构建一个复杂的企业应用程序,因为它非常可扩展、可维护且结构良好。
- 您正在构建一个大型长期项目的迁移,因为 Symfony 对未来六年有可预测的发布计划,因此不太可能出现任何意外。