作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
卡里姆Sakhibgareev的头像

卡里姆Sakhibgareev

曾与Laravel为Media工作.net和Symfony for Into Film, Karim熟悉这两个框架的优缺点.

Expertise

工作经验

20

Share

今天,当开始一个新项目时,一个关键的决定是选择正确的框架. 现在很难想象,如果没有它,我们将从头开始构建一个复杂的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 vs. 交响曲:竞争者

Laravel

当谈到Laravel时,我们指的是Laravel版本4及以后的版本. Laravel 4于2013年发布,代表了对框架的完全重写. 框架的功能被解耦到单独的组件中, 哪些是由Composer管理的, 而不是所有的东西都在一个巨大的代码存储库中.

Laravel宣称自己是一个快速开发的框架,语法简单优美,易于学习, read, and maintain. 它是2016年最流行的框架. According to Google trends,它的受欢迎程度是其他框架的三倍,等等 GitHub在美国,它的明星数量是竞争对手的两倍.

Symfony

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的安装就像下面这样简单:

下载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.

Laravel安装

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基本配置

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基本配置

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 文件和特定于组件的配置存储在 .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 vs. Laravel

Symfony的应用程序配置机制允许您为不同的环境创建不同的文件. 此外,它还可以防止在YAML配置中注入复杂的PHP逻辑.

However, 你可能会觉得Laravel使用的默认PHP配置语法更舒服,而且你不需要学习YAML语法.

路由与控制器

In general, 后端web应用程序有一个主要职责:读取每个请求并根据请求的内容创建响应. 控制器是一个类,负责通过调用应用程序方法将请求转换为响应, 而路由器是一种机制,它可以帮助您检测应该为特定请求执行哪个控制器类和方法.

让我们创建一个控制器,该控制器将显示从请求的博客文章页面 /posts/{id} route.

Laravel中的路由和控制器

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() 要呈现页面.

Symfony中的路由和控制器

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的情况相同.

路由和控制器:Symfony vs. Laravel

在这个阶段,你可能会认为Laravel比Symfony要好得多. 这在一开始是真的. 它看起来更好,更容易开始. 然而,在实际应用程序中,您不应该从控制器调用Doctrine. 相反,您应该调用一个服务,该服务将尝试查找post或throw HTTP 404 Exception.

Templates

Laravel附带了一个模板引擎 Blade 和Symfony发布的 Twig. 两个模板引擎都实现了两个主要特性:

  1. 模板继承
  2. 块或部分

这两个特性都允许您定义具有可重写部分和填充这些部分值的子模板的基本模板.

让我们再次考虑博客文章页面的示例.

Laravel刀片模板引擎

// 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示例? 您不需要在代码中知道它是从其他模板继承的. 这些都是在视图级别的模板中定义的.

Symfony树枝模板引擎

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

模板:Symfony vs. Laravel

从结构上讲,Blade和Twig模板非常相似. 两者都将模板生成为PHP代码并且工作速度很快,并且都实现控制结构,例如 if 语句和循环. 这两个引擎最重要的特性是默认情况下转义输出, 这有助于防止XSS攻击.

Aside from syntax, 主要区别在于,Blade允许您直接将PHP代码注入到模板中,而Twig不允许. 相反,Twig允许您使用过滤器.

例如,如果你想大写一个字符串,在Blade中你需要指定以下内容:

{{ucfirst('welcome friend')}}

另一方面,在Twig中,您将执行以下操作:

{{“欢迎朋友”|大写}}

In Blade, 扩展某些功能更容易, 但是Twig不允许在模板中直接写PHP代码.

依赖注入

应用程序有许多不同的服务和组件,具有各种相互依赖关系. 您需要以某种方式存储有关所创建对象及其依赖关系的所有信息.

这是下一个分量- Service Container. 它是一个PHP对象,用于创建所请求的服务并存储有关所创建对象及其依赖项的信息.

让我们考虑下面的例子:您正在创建一个类 PostService 实现一个负责创建新博客文章的方法. 这个类依赖于另外两个服务: PostRepository,它负责在数据库中存储信息 SubscriberNotifier,它负责通知订阅用户有关新帖子的信息. 要使其工作,需要将这两个服务作为构造函数参数传递给 PostService 或者,换句话说,您需要注入这些依赖项.

Symfony依赖注入示例

首先,让我们定义我们的示例服务:

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依赖注入示例

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

依赖注入:Symfony vs . Symfony. Laravel

Laravel的自动检测功能非常出色. Symfony有一个类似的功能,叫做“autowire,默认情况下是关闭的,可以通过添加 autowire: true 到您的依赖项配置,但它需要一些配置. Laravel的方法更简单.

对象关系映射(ORM)

为了处理数据库,这两个框架都附带了对象关系映射(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.

Doctrine

让我们从定义模型开始:

在这里,我们创建了模型映射信息,现在可以使用一个助手来生成方法存根:

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

Eloquent

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: Symfony vs. Laravel

关于ORM, Eloquent看起来更加友好 PHP developers 而且比教义更容易学.

事件调度程序vs. Middleware

Symfony vs. Laravel Lifecycle

了解框架最重要的事情之一是它的生命周期.

Symfony和事件分派器

为了将请求转换为响应,Symfony使用EventDispatcher. 它必然会触发不同的生命周期事件和特殊的事件侦听器来处理这些事件. 在开始时,它发送 kernel.request 包含请求信息的事件. 此事件的主要默认侦听器是 RouterListener,它调用router组件为当前请求查找合适的路由规则. 在此之后,将逐步执行其他事件. 典型的事件监听器包括安全检查、CSRF令牌验证和日志记录过程. 如果您想在请求生命周期中添加一些功能,您需要创建一个自定义 EventListener 并订阅必要的事件.

Laravel和中间件

Laravel使用了一种不同的解决方案:中间件. 我喜欢把中间件比作洋葱:应用程序有一定的层,请求在到达控制器和返回的过程中要经过这些层. So, 如果您想扩展应用程序逻辑并在请求生命周期中添加一些功能, 您需要在中间件列表中添加一个额外的层, Laravel将执行它.

REST API

让我们尝试创建一个基本的CRUD示例来管理博客文章:

  • Create - POST /posts/
  • Read - GET /posts/{id}
  • Update - PATCH /posts/{id}
  • Delete - 删除/文章/ {id}

Symfony中的REST API

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吗.

Laravel中的REST API

首先,需要定义路由. 你可以在 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: Symfony vs. Laravel

正如你所看到的,对于一个典型的REST API, Laravel比Symfony简单得多.

选择赢家:Symfony还是Laravel?

Laravel和Symfony之间没有明显的赢家,因为一切都取决于你的最终目标. (近年来也出现了一些Laravel和Symfony的替代品, 但它们超出了这个比较的范围.)

Laravel是一个更好的选择,如果:

  • 这是您第一次使用该框架, 因为它容易学习,语法更简单,学习材料也更好.
  • 你正在创建一个初创产品,并验证你的假设, 因为它有利于快速的应用程序开发和 Laravel开发者 are easy to find.

Symfony是最好的选择,如果:

  • 您正在构建一个复杂的企业应用程序, 因为它是可扩展的, maintainable, 结构良好.
  • 您正在构建一个大型长期项目的迁移, 因为Symfony在未来六年的发布计划是可以预测的, 所以不太可能出现任何意外.
聘请Toptal这方面的专家.
Hire Now
卡里姆Sakhibgareev的头像
卡里姆Sakhibgareev

Located in Porto, Portugal

Member since August 22, 2016

About the author

曾与Laravel为Media工作.net和Symfony for Into Film, Karim熟悉这两个框架的优缺点.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

工作经验

20

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.