作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
曾与Laravel为Media工作.net和Symfony for Into Film, Karim熟悉这两个框架的优缺点.
20
今天,当开始一个新项目时,一个关键的决定是选择正确的框架. 现在很难想象,如果没有它,我们将从头开始构建一个复杂的web应用程序.
许多流行的web开发语言都有自己的“默认”框架, 比如Ruby on Rails for Ruby, 或者Django for Python. 然而,PHP没有这样单一的默认值,它有多个流行的选项可供选择.
According to Google trends and GitHub,最流行的PHP框架是 Symfony with 13.7k stars and 有29k颗星的Laravel (在撰写本文时).
In this article, 我将比较这两个框架,并向您展示如何实现simple, 每个的日常功能. 通过这种方式,您可以并排比较实际示例的代码.
本文假定您具有较强的PHP技能并理解MVC架构范例, 但不需要Symfony或Laravel的经验.
当谈到Laravel时,我们指的是Laravel版本4及以后的版本. Laravel 4于2013年发布,代表了对框架的完全重写. 框架的功能被解耦到单独的组件中, 哪些是由Composer管理的, 而不是所有的东西都在一个巨大的代码存储库中.
Laravel宣称自己是一个快速开发的框架,语法简单优美,易于学习, read, and maintain. 它是2016年最流行的框架. According to Google trends,它的受欢迎程度是其他框架的三倍,等等 GitHub在美国,它的明星数量是竞争对手的两倍.
Symfony 2于2011年发布, 但它不能与Symfony 1混淆, 这是一个有着不同基本原则的完全不同的框架. Fabien Potencier创建了Symfony 2,目前的版本是3.2,这是Symfony 2的增量版本. 因此,它们通常被简单地称为Symfony2/3.
与Laravel 4一样,Symfony 2被设计为一组解耦组件. 这样做有两个好处:我们可以替换Symfony项目中的任何组件, 我们可以在非Symfony项目中使用任何Symfony组件. Symfony组件可以作为很好的代码示例,它们在很多 开源项目 such as Drupal、phpBB和Codeception. 事实上,Laravel本身使用了不少于14个Symfony组件. 因此,理解Symfony框架会给您在处理其他项目时带来很多好处.
两个框架都附带了安装程序和包装器,可通过 PHP内置web服务器.
Symfony的安装就像下面这样简单:
下载Symfony安装程序
sudo curl -LsS http://symfony./usr/local/bin/symfony
#授予执行安装程序的权限
执行命令chmod a+x /usr/local/bin/symfony
创建新的Symfony项目
Symfony new symfony_project
#启动内置服务器
cd symfony_project / && PHP bin/console server:启动
That’s it! 您的Symfony安装在URL上 http://localhost:8000
.
The Laravel installation process is almost the same and as simple as that for Symfony; the only difference between Laravel and Symfony installation is that you obtain Laravel’s installer through Composer:
#使用Composer下载Laravel安装程序
Composer global require“laravel/installer”
创建新的Laravel项目
新laravel_project
#启动内置服务器
cd laravel_project / && php artisan serve
You can now visit http://localhost:8000
检查您的Laravel安装.
Note: 默认情况下,Laravel和Symfony都使用相同的本地主机端口(8000), 因此,这些默认实例不能并发运行. 不要忘记通过运行来停止Symfony服务器 PHP bin/console server:停止
在启动Laravel服务器之前.
这些是基本安装的示例. 有关更高级的使用示例, 例如能够使用本地域配置项目或一次运行多个项目, 两个框架都提供了流浪汉盒子:
Symfony使用YAML作为指定其配置的语法. 默认配置位于 app/config/config.yml
文件,看起来像下面的例子:
imports:
—{resource:参数.yml }
—{resource:安全.yml }
—{resource: services.yml }
framework:
秘密 : '% 秘密%’
路由器:{资源:'%kernel.root_dir % / config /路由.yml' }
# ...
# Twig配置
twig:
调试 : '% 内核.debug%'
strict_variables:“%内核.debug%'
# ...
要创建特定于环境的配置,请创建该文件 应用程序/配置/ config_ENV.yml
包含基本配置参数. 这里有一个a的例子 config_dev.yml
开发环境文件:
imports:
—{resource: config.yml }
# ...
web_profiler:
toolbar: true
# ...
这个例子打开 web_profiler
Symfony工具仅适用于开发环境. 此工具可帮助您在浏览器窗口中调试和配置应用程序.
在配置文件中,您也可以注意到 %secret%
constructions. 它们允许我们将特定于环境的变量放在单独的 parameters.yml
file. 该文件在每台机器上可能是唯一的,并且不存储在版本控制下. 对于版本控制,我们有一个特殊的 parameters.yml.dist
的模板文件 parameters.yml
file.
这里有一个例子 parameters.yml
file:
parameters:
database_host: 127.0.0.1
database_port:零
database_name: symfony
database_user:根
database_password:零
秘密:f6b16aea89dc8e4bec811dea7c22d9f0e55593af
Laravel的配置看起来与Symfony非常不同. 它们唯一的共同点是它们都使用不在版本控制下存储的文件(.env
在Laravel的情况下)和用于生成此文件的模板(.env.example
). 这个文件有一个键和值的列表,就像下面的例子:
APP_ENV=local
APP_KEY = base64: Qm8mIaur5AygPDoOrU + IKecMLWgmcfOjKJItb7Im3Jk =
APP_DEBUG=true
APP_LOG_LEVEL =调试
APP_URL = http://localhost
和Symfony YAML文件一样,这个用于Laravel的文件也是人类可读的,看起来很干净. 您还可以创建 .env.testing
该文件将在运行PHPUnit测试时使用.
应用程序配置存储在 .php
files in the config
directory. 基本配置存储在 app.php
文件和特定于组件的配置存储在
files (e.g., cache.php
or mail.php
). 这里有一个a的例子 config/app.php
file:
'Laravel',
'env' => env('APP_ENV', 'production'),
'debug' => env('APP_DEBUG', false),
'url' => env('APP_URL', 'http://localhost'),
'timezone' => 'UTC',
'locale' => 'en',
// ...
];
Symfony的应用程序配置机制允许您为不同的环境创建不同的文件. 此外,它还可以防止在YAML配置中注入复杂的PHP逻辑.
However, 你可能会觉得Laravel使用的默认PHP配置语法更舒服,而且你不需要学习YAML语法.
In general, 后端web应用程序有一个主要职责:读取每个请求并根据请求的内容创建响应. 控制器是一个类,负责通过调用应用程序方法将请求转换为响应, 而路由器是一种机制,它可以帮助您检测应该为特定请求执行哪个控制器类和方法.
让我们创建一个控制器,该控制器将显示从请求的博客文章页面 /posts/{id}
route.
Controller
Post::findOrFail($id)]);
}
}
Router
路线:get(“/文章/ {id}”,“BlogController@show”);
我们已经定义了路线 GET
requests. URI匹配的所有请求 /posts/{id}
will execute the BlogController
controller’s show
方法,并将传递参数 id
to that method. 在控制器中,我们试图找到模型的对象 POST
with the passed id
,并呼叫Laravel助手 view()
要呈现页面.
In Symfony, exampleController
稍微大一点:
getDoctrine()->getRepository('BlogBundle:Post');
$post = $repository->find($id);
如果($post === null) {
throw $this->createNotFoundException();
}
return $this->render('BlogBundle:Post:show.html.twig', ['post'=>$post]);
}
}
你可以看到我们已经包括了 @Route(“/文章/ {id}”)
在注释中,我们只需要在 routing.yml
配置文件:
blog:
资源:“@BlogBundle /控制器/”
类型:注释
prefix: /
一步一步的逻辑与Laravel的情况相同.
在这个阶段,你可能会认为Laravel比Symfony要好得多. 这在一开始是真的. 它看起来更好,更容易开始. 然而,在实际应用程序中,您不应该从控制器调用Doctrine. 相反,您应该调用一个服务,该服务将尝试查找post或throw HTTP 404异常.
Laravel附带了一个模板引擎 Blade 和Symfony发布的 Twig. 两个模板引擎都实现了两个主要特性:
这两个特性都允许您定义具有可重写部分和填充这些部分值的子模板的基本模板.
让我们再次考虑博客文章页面的示例.
// base.blade.php
@section('page-title')
Welcome to blog!
@show
@yield('title')
@yield('content')
// post.blade.php
@extends('base')
@section('page-title')Post {{ $post->-在我们的博客中阅读这篇文章和更多内容.@endsection
@section('title'){{ $post->title }}@endsection
@section(“内容”)
{{ $post->content }}
@endsection
现在你可以在你的控制器中告诉Laravel渲染模板了 post.blade.php
. 你还记得我们的 view(‘post’, …)
调用前面的Controller示例? 您不需要在代码中知道它是从其他模板继承的. 这些都是在视图级别的模板中定义的.
// base.html.twig
{% block page_title %}
Welcome to blog!
{% endblock %}
{% block title %}{% endblock %}
{%块内容%}{%结束块%}
// show.html.twig
{%扩展'@Blog/base.html.twig' %}
{% block page_title %}Post {{Post.-在我们的博客中阅读这篇文章和更多内容.{% endblock %}
{%块标题%}{{post.Title}}{% endblock %}
{%块内容%}
{{ post.content }}
{% endblock %}
从结构上讲,Blade和Twig模板非常相似. 两者都将模板生成为PHP代码并且工作速度很快,并且都实现控制结构,例如 if
语句和循环. 这两个引擎最重要的特性是默认情况下转义输出, 这有助于防止XSS攻击.
Aside from syntax, 主要区别在于,Blade允许您直接将PHP代码注入到模板中,而Twig不允许. 相反,Twig允许您使用过滤器.
例如,如果你想大写一个字符串,在Blade中你需要指定以下内容:
{{ucfirst('welcome friend')}}
另一方面,在Twig中,您将执行以下操作:
{{'welcome friend'|大写}}
In Blade, 扩展某些功能更容易, 但是Twig不允许在模板中直接写PHP代码.
应用程序有许多不同的服务和组件,具有各种相互依赖关系. 您需要以某种方式存储有关所创建对象及其依赖关系的所有信息.
这是下一个分量- Service Container. 它是一个PHP对象,用于创建所请求的服务并存储有关所创建对象及其依赖项的信息.
让我们考虑下面的例子:您正在创建一个类 PostService
实现一个负责创建新博客文章的方法. 这个类依赖于另外两个服务: PostRepository
,它负责在数据库中存储信息 SubscriberNotifier
,它负责通知订阅用户有关新帖子的信息. 要使其工作,需要将这两个服务作为构造函数参数传递给 PostService
或者,换句话说,您需要注入这些依赖项.
首先,让我们定义我们的示例服务:
repository = $repository;
$this->notifier = $notifier;
}
创建Post $ Post
{
$this->repository->persist($post);
$this->notifier->notifyCreate($post);
}
}
接下来是依赖注入配置:
# src / BlogBundle /资源/ config /服务.yml
services:
#我们的主要服务
blog.post_service:
类:BlogBundle \ \ PostService服务
参数:[' @blog.post_repository”、“@blog.subscriber_notifier ']
# subscribernotification服务. 它也可以有自己的依赖项,例如,mailer类.
blog.subscriber_notifier:
类:BlogBundle \ \ SubscriberNotifier服务
# Repository. 不要深入研究它的结构,它现在不是一个主题
blog.post_repository:
类:BlogBundle \ Repository \ PostRepository
工厂:“学说.orm.default_entity_manager: getRepository
arguments:
——BlogBundle \实体\
现在,您可以从服务容器对象请求代码中的任何位置的post服务. 例如,在控制器中,它可能是这样的:
//控制器文件. $post变量定义如下
$this->get('blog.post_service')->create($post);
服务容器是一个很好的组件,它可以帮助您构建以下应用程序 SOLID design principles.
在Laravel中管理依赖关系要容易得多. 让我们考虑同样的例子:
repository = $repository;
$this->notifier = $notifier;
}
创建Post $ Post
{
$this->repository->persist($post);
$this->notifier->notifyCreate($post);
}
}
这就是Laravel的美丽 您不需要创建依赖项配置. Laravel自动扫描依赖项 PostService
在其构造函数中对参数进行类型并自动解析.
你也可以在你的控制器方法中使用inject PostService
通过在方法参数中“类型提示”:
'Title', 'content' => 'Content']);
$service->create($post);
返回重定向(' /文章/ '.$post->id);
}
}
Laravel的自动检测功能非常出色. Symfony有一个类似的功能,叫做“autowire,默认情况下是关闭的,可以通过添加 autowire: true
到您的依赖项配置,但它需要一些配置. Laravel的方法更简单.
为了处理数据库,这两个框架都附带了对象关系映射(Object-Relational Mapping, ORM)特性. ORM将记录从数据库映射到代码中的对象. 为此,您必须为数据库中的每个记录类型(或每个表)创建模型.
Symfony使用第三方项目 Doctrine 来与数据库交互,而Laravel使用自己的库 Eloquent.
Eloquent ORM实现 ActiveRecord模式 使用数据库. 在此模式中,每个模型都知道与数据库的连接,并可以与之交互. 例如,它可以将数据保存到数据库,更新或删除记录.
教义实现了 数据映射器模式, where models know nothing about the database; they are only aware of the data itself. 一个特殊的独立层, EntityManager
, 存储关于模型和数据库之间交互的所有信息, 它处理所有的操作.
让我们举个例子来理解它们的区别. 假设你的模型有一个主要的 id
键、标题、内容和作者. The Posts 表只存储作者 id
,因此您需要创建到 Users table.
让我们从定义模型开始:
在这里,我们创建了模型映射信息,现在可以使用一个助手来生成方法存根:
php bin/console原则:生成:实体BlogBundle
接下来,我们定义post repository方法:
getEntityManager()->persist($post);
$this->getEntityManager()->flush();
}
/**
*搜索给定作者姓名的帖子
*
@参数字符串$name
* @return array
*/
findByAuthorName($name)
{
return $this->createQueryBuilder('posts')
->select('posts')
->join('posts.作者”、“作者”)
->where('author.name = :name')
->setParameter('name', $name)
->getQuery()
->getResult();
}
}
现在您可以从服务中调用这些方法,例如,从 PostController
:
//查询职位
$posts = $this->getDoctrine()->getRepository('BlogBundle:Post')->findByAuthorName('Karim');
//在数据库中保存新文章
$this->getDoctrine()->getRepository('BlogBundle:Post')->persist($post);
The User 模型与Laravel一起发布,它是默认定义的,所以你只需要为 Post.
belongsTo('App\User', 'author_id');
}
}
这就是模特的全部. In Eloquent, 你不需要定义模型属性, 因为它基于数据库表结构动态地构建它们. 存储一个新帖子 $post
在数据库中,您需要进行以下调用(例如,从控制器):
$post->save();
查找具有给定名称的作者的所有帖子, 最好的方法是找到一个用户的名字,并请求所有用户的帖子:
$posts = Post::whereHas('author', function ($q) {
$q->where('name', 'Karim');
})->get();
关于ORM, Eloquent看起来更加友好 PHP developers 而且比教义更容易学.
了解框架最重要的事情之一是它的生命周期.
为了将请求转换为响应,Symfony使用EventDispatcher. 它必然会触发不同的生命周期事件和特殊的事件侦听器来处理这些事件. 在开始时,它发送 kernel.request
包含请求信息的事件. 此事件的主要默认侦听器是 RouterListener
,它调用router组件为当前请求查找合适的路由规则. 在此之后,将逐步执行其他事件. 典型的事件监听器包括安全检查、CSRF令牌验证和日志记录过程. 如果您想在请求生命周期中添加一些功能,您需要创建一个自定义 EventListener
并订阅必要的事件.
Laravel使用了一种不同的解决方案:中间件. 我喜欢把中间件比作洋葱:应用程序有一定的层,请求在到达控制器和返回的过程中要经过这些层. So, 如果您想扩展应用程序逻辑并在请求生命周期中添加一些功能, 您需要在中间件列表中添加一个额外的层, Laravel将执行它.
让我们尝试创建一个基本的CRUD示例来管理博客文章:
POST /posts/
GET /posts/{id}
PATCH /posts/{id}
删除/文章/ {id}
Symfony没有一个简单的开箱即用的解决方案来快速创建REST API, 但它有很棒的第三方捆绑包 FOSRestBundle
and JMSSerializerBundle
.
让我们考虑最小的工作示例 FOSRestBundle
and JMSSerializerBundle
. 在你安装它们并打开它们之后 AppKernel
, 你可以在bundle配置中设置你将使用JSON格式,并且它不必包含在URL请求中:
# app / config / config.yml
fos_rest:
routing_loader:
default_format: json
include_format:假
在路由配置中, 你应该指定这个控制器将实现一个REST资源:
# app / config /路由.yml
blog:
资源:BlogBundle \ \为PostController控制器
type: rest
You implemented a persist method in the repository in the previous example; now you need to add a delete method:
/ / src / BlogBundle /仓库/ PostRepository.php
删除Post $ Post
{
$this->getEntityManager()->remove($post);
$this->getEntityManager()->flush();
}
接下来,您需要创建一个 form class 接受输入请求并将其映射到模型. 你可以通过使用CLI帮助器来完成:
php bin/console原则:generate:form BlogBundle:Post
您将收到一个生成的表单类型,包含以下代码:
add('title')->add('content');
}
/**
* {@inheritdoc}
*/
配置选项(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'BlogBundle\Entity\Post',
'csrf_protection' => false
]);
}
/**
* {@inheritdoc}
*/
getBlockPrefix()
{
return 'post';
}
}
现在我们来实现控制器.
Note: 我将向您展示的代码并不完美. 它违反了一些设计原则,但可以很容易地重构. 主要目的是向您展示如何一步一步地实现每个方法.
getDoctrine()->getRepository('BlogBundle:Post')->find($id);
如果($post === null) {
$view->setStatusCode(Response::HTTP_NOT_FOUND);
} else {
$view->setData(['post' => $post]);
}
return $this->handleView($view);
}
/**
* @param请求$请求
* @return响应
*/
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请求$请求
* @return响应
*/
patchPostAction($id,请求$ Request)
{
$view = new view (null, Response::HTTP_BAD_REQUEST);
$post = $this->getDoctrine()->getRepository('BlogBundle:Post')->find($id);
如果($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响应
*/
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);
}
}
With FOSRestBundle
, you don’t need to declare a route for each method; just follow the convention with controller method names, and JMSSerializerBundle
会自动将你的模型转换为JSON吗.
首先,需要定义路由. 你可以在 API
部分的路由规则,以关闭一些默认中间件组件并打开其他组件. The API
节位于 routes/api.php
file.
在模型中,您应该定义 $fillable
属性在模型的create和update方法中传递变量:
现在让我们定义控制器:
get('post'));
return response(null, Response::HTTP_CREATED, ['Location'=>'/posts/'.$post->id]);
}
公共函数更新(Post $ Post, Request $ Request)
{
$post->update($request->get('post'));
return response(null, Response::HTTP_NO_CONTENT, ['Location'=>'/posts/'.$post->id]);
}
销毁Post $ Post的公共函数
{
$post->delete();
返回响应(null, response::HTTP_NO_CONTENT);
}
}
在Symfony中,您使用 FosRestBundle
,它将错误封装在JSON中. 在Laravel,你需要自己动手. 你需要更新Exception处理程序中的render方法,以便在预期JSON请求时返回JSON错误:
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);
}
返回parent::render($request, $exception);
}
// ...
}
正如你所看到的,对于一个典型的REST API, Laravel比Symfony简单得多.
Laravel和Symfony之间没有明显的赢家,因为一切都取决于你的最终目标. (近年来也出现了一些Laravel和Symfony的替代品, 但它们超出了这个比较的范围.)
Laravel是一个更好的选择,如果:
Symfony是最好的选择,如果:
曾与Laravel为Media工作.net和Symfony for Into Film, Karim熟悉这两个框架的优缺点.
20
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.