个性化阅读
专注于IT技术分析

PHP框架:在Symfony和Laravel之间进行选择

本文概述

今天, 当开始一个新项目时, 关键的决定之一就是选择正确的框架。如今, 很难想象没有一个从头开始构建复杂的Web应用程序。

许多用于Web开发的流行语言都有其”默认”框架, 例如用于Ruby的Ruby on Rails或用于Python的Django。但是, PHP没有这样的默认设置, 并且有多个常用选项可供选择。

根据Google的趋势和GitHub, 最受欢迎的PHP框架是具有13.7万星的Symfony和具有29k星的Laravel(在撰写本文时)。

在本文中, 我将比较这两个框架, 并向你展示如何分别实现简单的日常功能。这样, 你可以并排比较实际示例的代码。

本文假定你具有强大的PHP技能和对MVC架构范例的理解, 但是不需要先前具有Symfony或Laravel的经验。

竞争者

拉拉韦尔

在谈到Laravel时, 我们指的是Laravel版本4及更高版本。 Laravel 4于2013年发布, 代表了对框架的完全重写。框架的功能被分离为单独的组件, 这些组件由Composer管理, 而不是将所有组件都放在一个庞大的代码存储库中。

Laravel宣称自己是一个快速发展的框架, 它具有简单易懂的语法, 易于学习, 阅读和维护。它是2016年最受欢迎的框架。根据Google的趋势, 它的流行度是其他框架的三倍, 而在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组件可以作为出色的代码示例, 并且在许多开源项目(例如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

而已!你可以在URL http:// localhost:8000上使用Symfony安装。

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都在同一本地主机端口(8000)上运行, 因此你不能让这些默认实例同时运行。在启动Laravel服务器之前, 请不要忘记通过运行php bin / console服务器来停止Symfony服务器:

关于框架安装

这些是基本安装的示例。对于更高级的用法示例, 例如能够使用本地域配置项目或一次运行多个项目, 这两个框架都提供了Vagrant框:

  • Laravel Homestead,
  • Symfony Homestead。

基本框架配置

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%构造。它们使我们可以将特定于环境的变量放在单独的parameter.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的配置完全不同。它们唯一的共同点是它们都使用了不在版本控制下存储的文件(在Laravel中为.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的该文件也易于阅读, 看上去也很干净。你还可以创建.env.testing文件, 该文件将在运行PHPUnit测试时使用。

应用程序配置存储在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}', '[email protected]');

我们已经定义了GET请求的路由。 URI匹配/ posts / {id}的所有请求都将执行BlogController控制器的show方法, 并将参数id传递给该方法。在控制器中, 我们尝试查找带有传递的id的POST模型的对象, 并调用Laravel helper 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异常的服务。

范本

Laravel附带了名为Blade的模板引擎, 而Symfony附带了Twig。这两个模板引擎都实现了两个主要功能:

  1. 模板继承
  2. 块或节

这两个功能都允许你定义具有可覆盖部分的基本模板以及填充这些部分的值的子模板。

让我们再次考虑博客帖子页面的示例。

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 [email protected]

@section('title'){{ $post->title }}@endsection

@section('content')
    {{ $post->content }}
@endsection

现在, 你可以告诉Controller中的Laravel渲染模板post.blade.php。你还记得上一个Controller示例中的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);

服务容器是一个很棒的组件, 它有助于遵循SOLID设计原则来构建你的应用程序。

相关:具有Symfony组件的真正依赖注入

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的优点-你无需创建依赖项配置。 Laravel在其构造函数参数类型中自动扫描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具有类似的功能, 称为”自动装配”, 默认情况下处于关闭状态, 可以通过添加自动装配来打开:true依赖项配置, 但需要一些配置。 Laravel方法更简单。

对象关系映射(ORM)

为了使用数据库, 这两个框架都具有对象关系映射(ORM)功能。 ORM将记录从数据库映射到代码中的对象。为此, 必须为数据库中的每种记录类型(或每种表)创建模型。

Symfony使用第三方项目Doctrine与数据库进行交互, 而Laravel使用其自己的库Eloquent。

雄辩的ORM实施ActiveRecord模式以与数据库一起使用。在这种模式下, 每个模型都知道与数据库的连接并可以与其进行交互。例如, 它可以将数据保存到数据库, 更新或删除记录。

Doctrine实现了Data Mapper模式, 其中模型对数据库一无所知。他们只知道数据本身。特殊的单独层EntityManager存储有关模型和数据库之间交互的所有信息, 并处理所有操作。

让我们举个例子来了解差异。假设你的模型具有主ID密钥, 标题, 内容和作者。 “帖子”表仅存储作者ID, 因此你需要创建与”用户”表的关系。

教义

首先定义模型:

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

雄辩

Laravel附带有User模型, 并且默认情况下已定义该模型, 因此你只需为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与拉拉韦尔

关于ORM, 与Doctrine相比, Eloquent对PHP开发人员而言更加友好并且更易于学习。

事件调度程序与中间件

Symfony与Laravel生命周期

要了解框架, 最重要的事情之一就是框架的生命周期。

Symfony和事件调度程序

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

Laravel和中间件

Laravel使用另一种解决方案:中间件。我喜欢将中间件与洋葱进行比较:你的应用程序具有某些层, 并且请求在通过这些层的过程中将传递到控制器并返回。因此, 如果你想扩展应用程序逻辑并在请求生命周期中添加一些功能, 则需要在中间件列表中添加一个附加层, Laravel将执行它。

REST API

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

  • 创建-POST /帖子/
  • 阅读-GET /帖子/ {id}
  • 更新-PATCH / posts / {id}
  • 删除-删除/ 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方法。现在你需要添加一个delete方法:

// 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制定了未来六年的可预测发布计划, 因此不太可能出现任何意外。
赞(0)
未经允许不得转载:srcmini » PHP框架:在Symfony和Laravel之间进行选择

评论 抢沙发

评论前必须登录!