Laravel主义-两全其美?

Laravel Doctrine is a drop-in implementation of the famous ORM for the Laravel 5.X Framework, and a really interesting alternative to the default choice, Eloquent. In this article, we will learn how to use it, and when.

Laravel主义是一个下拉实施著名的ORM为的Laravel 5.X框架 ,以及一个非常有趣的替代默认的选择, 雄辩 。 在本文中,我们将学习如何使用它以及何时使用。

alt

为什么是教义而不是口才? (Why Doctrine, and not Eloquent?)

Those of us who have used Laravel at least once, have probably used Eloquent. We all love its fluent and easy syntax, and we all know snippets like the following:

我们当中至少使用过Laravel一次的人可能使用过Eloquent。 我们都喜欢它的流利且简单的语法,并且我们都知道如下代码段:

$user = new App\User;
$user->name = 'Francesco';
$user->email = 'francesco@foo.bar';
$user->save();

What we’re using here is the so called “Active Record” pattern. A frequently used architectural pattern, named by Martin Fowler in his ultra-famous book “Patterns of Enterprise Application Architecture”. To simplify and explain things, this pattern assumes that a single row in the database is treated like an object in our software. However, with time, this pattern encountered many criticisms:

我们在这里使用的是所谓的“活动记录”模式。 一种常用的架构模式,由Martin Fowler在他超著名的书“企业应用程序架构模式”中命名。 为了简化和解释,此模式假定数据库中的单行被视为软件中的对象。 但是,随着时间的流逝,这种模式遭到了许多批评:

  • Active Record is about a strong coupling between database operations and the classes in our software. For many little projects this approach is more than fine, but what if our software grows in complexity? We could stumble upon the need for more classes, not always related to a specific table in our database. There, Active Record doesn’t help us achieve a good level of abstraction from the data source.

    Active Record是关于数据库操作与我们软件中的类之间的强大耦合。 对于许多小项目来说,这种方法还不错,但是如果我们的软件变得越来越复杂怎么办? 我们可能偶然发现需要更多类,而这些类并不总是与数据库中的特定表相关。 在那里,Active Record不能帮助我们从数据源中获得良好的抽象水平。

  • the other main criticism is a direct consequence of the first: testability. If the model is tightly coupled to the database, building a test without it can be more difficult. It has to be said that a part of this problem can be reduced (not solved) with mocking and a good use of dependency injection.

    另一个主要的批评是第一个的直接后果:可测试性。 如果模型与数据库紧密耦合,那么在没有模型的情况下进行测试可能会更加困难。 必须说,可以通过嘲笑和良好地使用依赖项注入来减少(未解决)此问题的一部分。

Now, in the same book we mentioned before, Martin Fowler explained another architectural pattern: the Data Mapper. The Data Mapper consists of the presence of an intermediate layer which, working in both directions, provides access to the data source on one hand, and a good abstraction from it on the other. This means that objects in the software are not closely related to the data source, with great improvement in terms of responsibility isolation. The result? The developer can now focus on building an object that is nearer to the real world situation, and not to the database system chosen for the job.

现在,在我们前面提到的同一本书中,Martin Fowler解释了另一种架构模式: Data Mapper 。 数据映射器由一个中间层组成,该中间层在两个方向上均起作用,一方面提供对数据源的访问,另一方面又提供对数据源的良好抽象 。 这意味着软件中的对象与数据源并不紧密相关,在责任隔离方面有了很大的改进。 结果? 开发人员现在可以专注于构建更接近实际情况的对象,而不是为工作选择的数据库系统。

Eloquent is an implementation of the Active Record pattern, and Doctrine is an implementation of Data Mapper. Let’s see how we can install Doctrine for Laravel, and how to configure and use it.

口才是Active Record模式的实现,而Doctrine是Data Mapper的实现。 让我们来看看如何为Laravel安装Doctrine,以及如何配置和使用它。

为Laravel安装学说 (Installing Doctrine for Laravel)

As usual, we will use Homestead Improved as a standard development environment for our tests.

与往常一样,我们将使用Homestead Improvement作为测试的标准开发环境。

Let’s create a new Laravel project.

让我们创建一个新的Laravel项目。

composer create-project laravel/laravel Project

Then, we enter our project’s folder and add Laravel Doctrine as a dependency with Composer.

然后,我们进入项目的文件夹,并添加Laravel Doctrine作为Composer的依赖项。

composer require "laravel-doctrine/orm:1.1.*"
Successful installation of laravel doctrine

We also need to add the following class to our service providers list, in the config/app.php file:

我们还需要在config/app.php文件中将以下类添加到我们的服务提供商列表中:

LaravelDoctrine\ORM\DoctrineServiceProvider::class,

Also, we can register the three facades for the EntityManager, Registry and Doctrine in the same file:

另外,我们可以在同一文件中注册EntityManager,Registry和Doctrine的三个外观:

'EntityManager' => LaravelDoctrine\ORM\Facades\EntityManager::class,
'Registry'      => LaravelDoctrine\ORM\Facades\Registry::class,
'Doctrine'      => LaravelDoctrine\ORM\Facades\Doctrine::class,

Finally, we can publish the dedicated config file:

最后,我们可以发布专用的配置文件:

artisan vendor:publish --tag="config"

We are done! Laravel Doctrine is completely installed and configured.

我们完了! Laravel学说已完全安装和配置。

示例应用程序-待办事项清单! (The Example Application – ToDo List!)

In order to learn something more about Doctrine for Laravel, we are going to replicate an existing application example that uses Eloquent. What about the Intermediate Task List on Laravel’s official site?

为了了解有关Laravel Doctrine的更多信息,我们将复制一个使用Eloquent的现有应用程序示例。 那么Laravel官方网站上的“ 中级任务列表 ”呢?

We will create a very basic multi-user task list. In this application, our user will be able to:

我们将创建一个非常基本的多用户任务列表。 在此应用程序中,我们的用户将能够:

  • log into their own area.

    登录到自己的区域。
  • list existing tasks.

    列出现有任务。
  • add a new task to the list.

    将新任务添加到列表中。
  • mark a task as done / not done.

    将任务标记为已完成/未完成。
  • update an existing task in the list.

    更新列表中的现有任务。
  • delete an existing task from the list.

    从列表中删除现有任务。

Obviously, every user will be able to see only their own tasks.

显然,每个用户只能看到他们自己的任务。

Let’s change the application’s namespace to TodoList first.

首先,将应用程序的名称空间更改为TodoList

artisan app:name TodoList

Ok, now we can start from the very basics of Doctrine. In Eloquent, we used models. How do we start now?

好的,现在我们可以从“教义”的基础知识开始。 在Eloquent中,我们使用了模型。 我们现在如何开始?

实体! (Entities!)

In Doctrine we use Entities to represent our application objects. Unlike with Eloquent where the model extends a base Model class, a Doctrine entity is a plain PHP class that does not extend anything.

在Doctrine中,我们使用实体来表示我们的应用程序对象。 与Eloquent的模型扩展基本的Model类不同,Doctrine实体是不扩展任何内容的普通PHP类。

Here’s a first stub of the Task entity:

这是Task实体的第一个存根:

<?php

namespace TodoList\Entities;

class Task
{
    private $id;
    private $name;
    private $description;
    private $isDone = false;

    public function __construct($name, $description)
    {
        $this->name = $name;
        $this->description = $description;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getDescription()
    {
        return $this->description;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function setDescription($description)
    {
        $this->description = $description;
    }

    public function isDone()
    {
        return $this->isDone;
    }

    public function toggleStatus()
    {
        if(!$this->isDone) {
            $this->isDone = true;
        } else {
            $this->isDone = false;
        }
    }
}

Not bad as a beginning. Yes, it’s a little more “verbose” than an Eloquent model. However, there is a specific reason for that. First of all, if we think about it, this is a plain PHP class. It says nothing about its implementation, has no extra responsibilities. Everything it does is strictly related to the task itself. That’s a good thing, because this approach is more “compatible” with the Single Responsibility Principle.

作为开始,还不错。 是的,它比雄辩的模型“更冗长”。 但是,这是有特定原因的。 首先,如果我们考虑一下,这是一个普通PHP类。 它没有说明其实施,也没有额外的责任。 它所做的一切都与任务本身密切相关。 这是一件好事,因为这种方法与“ 单一责任原则 ”更“兼容”。

But…

但…

数据库在哪里? (Where is the Database?)

In Eloquent, models are just an interface to the database. With some “magic”, sometimes without even writing a single line of code in the model, we can just start working with them. The only thing we have to do is design our database accordingly. With Doctrine, the key concept is a little bit different.

在Eloquent中,模型只是数据库的接口。 有了一些“魔术”,有时甚至不需要在模型中编写一行代码,我们就可以开始使用它们。 我们唯一要做的就是相应地设计数据库。 使用Doctrine,关键概念有所不同。

Here, we start designing our application from our classes which represent the “real world” problem we want to solve. Then, we bind our classes to tables, and properties to columns.

在这里,我们从我们的类开始设计应用程序,这些类代表了我们要解决的“现实世界”问题。 然后,我们将类绑定到表,将属性绑定到列。

In Doctrine, we can do this in several ways. For this guide, we will use the “annotation” approach:

在《主义》中,我们可以通过多种方式来做到这一点。 对于本指南,我们将使用“注释”方法:

<?php

namespace TodoList\Entities;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="tasks")
 */
class Task
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $name;

    /**
     * @ORM\Column(type="string")
     */
    private $description;

    /**
     * @ORM\Column(type="boolean")
     */
    private $isDone = false;

With these annotations (which we can find in the Doctrine\ORM\Mapping class), we bound the class and its properties to a specific structure in the database. In this case, to a tasks table that has an id, name and description field.

使用这些注释(可以在Doctrine\ORM\Mapping类中找到),将类及其属性绑定到数据库中的特定结构。 在这种情况下, tasks表将具有idnamedescription字段。

The only remaining thing to do now, is to build the database starting from this structure. Doctrine can do this for us:

现在剩下要做的唯一事情就是从此结构开始构建数据库。 教义可以为我们做到这一点:

artisan doctrine:schema:create
#  Creating database schema for default entity manager...
#  Database schema created successfully!

Let’s check what happened in the database with a tool of our choice (or from the CLI).

让我们使用我们选择的工具(或从CLI)检查数据库中发生了什么。

Database changes successfully applied - screencap from MySQL Workbench

We got it!

我们得到了它!

Here’s what happened:

这是发生了什么:

  • with annotations, we added some meta data in our class, without really modifying it in a significant way. Annotations are comments, after all.

    通过注释,我们在类中添加了一些元数据,而没有真正地对其进行重大修改。 毕竟注释是注释。
  • by executing the artisan doctrine:schema:create command, Doctrine automatically scans entities for ORM annotations. When found, it creates and modifies the database schema accordingly;

    通过执行artisan doctrine:schema:create命令,Doctrine会自动扫描实体以查找ORM注释。 找到后,它将相应地创建和修改数据库模式。

Sounds great, right? Now we can start working with it. Let’s do some tests before diving deeper.

听起来不错,对吗? 现在我们可以开始使用它了。 让我们先做一些测试,然后再深入。

实体经理 (The Entity Manager)

Now that we have a database for our application, we should start figuring out how to insert new tasks and read them. For this purpose, at a basic level, we can use the EntityManager class.

现在我们已经为应用程序提供了数据库,我们应该开始弄清楚如何插入和读取新任务。 为此,在基本级别上,我们可以使用EntityManager类。

Let’s add a new route to our routes file:

让我们向路由文件添加一条新路由:

Route::get('test-add', function () {
    $task = new \TodoList\Entities\Task('Make test app', 'Create the test application for the Sitepoint article.');

    \EntityManager::persist($task);
    \EntityManager::flush();

    return 'added!';
});

… and execute it by browsing to the test-add URL. Once done, we verify the data.

…并通过浏览到test-add URL来执行它。 完成后,我们将验证数据。

First record successfully inserted

It worked!

有效!

The EntityManager is a special class which takes instances of entities and “translates” them into database records. As we can clearly see, the persistence responsibility is not related to the model/entity any more. But why two separate methods? What about the good old save? Well, things are a little different.

EntityManager是一个特殊的类,它接受实体的实例并将其“转换”为数据库记录。 我们可以清楚地看到,持久性责任不再与模型/实体相关。 但是,为什么要使用两种单独的方法? 好的旧save呢? 好吧,情况有所不同。

  • the persist method is used to give a specific instruction to the EntityManager class. It says “hey EntityManager, when I give you the signal, persist the entity”.

    persist方法用于为EntityManager类提供特定的指令。 它说:“嘿,EntityManager,当我向您发出信号时,请保留该实体”。

  • the flush method is used to execute the instruction we previously gave the EntityManager – the signal it needed.

    flush方法用于执行我们之前给EntityManager发出的指令-它需要的信号。

This is more flexible because we can also choose to make many modifications to many entities, and then finally call a single flush method for a specific “snapshot”. Of course, we can also use the Laravel Service Container to use the EntityManager:

这更加灵活,因为我们还可以选择对许多实体进行许多修改,然后最终为特定的“快照”调用单个flush方法。 当然,我们也可以使用Laravel服务容器来使用EntityManager:

Route::get('test-add', function (\Doctrine\ORM\EntityManagerInterface $em) {
    $task = new \TodoList\Entities\Task('Make test app', 'Create the test application for the Sitepoint article.');

    $em->persist($task);
    $em->flush();

    return 'added!';
});

The EntityManager is not just about adding entities. We can easily use it to find instances of existing ones. Let’s try to find the one we just added, in another find-test route.

EntityManager不仅仅是添加实体。 我们可以轻松地使用它来查找现有实例。 让我们尝试在另一条find-test路线中find-test刚刚添加的路线。

Route::get('test-find', function (\Doctrine\ORM\EntityManagerInterface $em) {
    /* @var \TodoList\Entities\Task $task */
    $task = $em->find(TodoList\Entities\Task::class, 1);

    return $task->getName() . ' - ' . $task->getDescription();
});

If we check the URL, sure enough, that’s our previously inserted item!

如果我们检查URL,果然是我们先前插入的项目!

应用程序 (The Application)

Ok, time to move on from the basics and start having some fun with our intended real project.

好的,是时候从基础开始学习,并开始对我们预期的真实项目有所乐趣。

We are going to start with the Tasks area. We will learn how to add, edit, remove and list them. Then, we will “protect” our items by introducing a user system in which every user will only see their own tasks.

我们将从“任务”区域开始。 我们将学习如何添加,编辑,删除和列出它们。 然后,我们将通过引入一个用户系统来“保护”我们的物品,其中每个用户只能看到自己的任务。

任务区 (The Tasks Area)

Let’s start by creating a dedicated controller for Tasks.

让我们开始为Tasks创建一个专用控制器。

artisan make:controller TaskController

We can leave it empty for now. In the resources/views folder, let’s create a very basic master layout that every view will use. We will call it master.blade.php.

我们现在可以将其留空。 在resources/views文件夹中,让我们创建每个视图都将使用的非常基本的主布局。 我们将其称为master.blade.php

<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>@yield('title') :: ToDo List!</title>

        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">
    </head>

    <body>
        <div class="container">
            <h1>ToDo List!</h1>
            <hr>

            @if(session('success_message'))
                <div class="alert alert-success alert-dismissible" role="alert">
                    <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                    {{ session('success_message') }}
                </div>
            @endif

            @yield('content')
        </div>

        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
    </body>
</html>

The next step is creating the form. Let’s create a new file named add.blade.php.

下一步是创建表单。 让我们创建一个名为add.blade.php的新文件。

@extends('master')

@section('title') Add a Task @endsection

@section('content')
    <div class="row">
        <div class="col-md-12">
            <h3>Add Task</h3>
            <p>Use the following form to add a new task to the system.</p>

            <hr>

            <form action="{{ url('add') }}" method="post">
                {{ csrf_field() }}

                <p><input autofocus type="text" placeholder="Name..." name="name" class="form-control" /></p>
                <p><input type="text" placeholder="Description..." name="description" class="form-control" /></p>

                <hr>

                <p><button class="form-control btn btn-success">Add Task</button></p>
            </form>
        </div>
    </div>
@endsection

Finally, the controller:

最后,控制器:

<?php

namespace TodoList\Http\Controllers;

use TodoList\Entities\Task;
use Illuminate\Http\Request;
use Doctrine\ORM\EntityManagerInterface;

class TaskController extends Controller
{
    public function getAdd()
    {
        return view('add');
    }

    public function postAdd(Request $request, EntityManagerInterface $em)
    {
        $task = new Task(
            $request->get('name'),
            $request->get('description')
        );

        $em->persist($task);
        $em->flush();

        return redirect('add')->with('success_message', 'Task added successfully!');
    }
}

The postAdd method has the logic we need to add a new item. As seen above, we are creating our entity and then saving it with persist followed by flush.

postAdd方法具有我们需要添加新项目的逻辑。 如上所示,我们正在创建我们的实体,然后将其保存为persist然后是flush

To read them, let’s create a new view, named tasks.blade.php:

要阅读它们,让我们创建一个新的视图,名为tasks.blade.php

@extends('master')

@section('title') Tasks List @endsection

@section('content')
    <div class="row">
        <div class="col-md-12">
            <h3>Tasks List</h3>

            <table class="table table-striped">
                <tr>
                    <th>ID</th>
                    <th>Name</th>
                    <th>Description</th>
                    <th>Status</th>
                    <th>Operations</th>
                </tr>

                @forelse($tasks as $task)
                    <tr>
                        <td>{{ $task->getId() }}</td>
                        <td>{{ $task->getName() }}</td>
                        <td>{{ $task->getDescription() }}</td>
                        <td>
                            @if($task->isDone())
                                Done
                            @else
                                Not Done
                            @endif
                        </td>
                        <td></td>
                    </tr>
                @empty
                    <tr>
                        <td colspan="5">No tasks in the list... for now!</td>
                    </tr>
                @endforelse
            </table>
        </div>
    </div>
@endsection

$tasks will contain the tasks list. Every element of this list is an instance of the Task entity we built before. No magic methods, no properties that we don’t remember… just methods we have created, keeping it clean! Now, to the controller!

$tasks将包含任务列表。 该列表的每个元素都是我们之前构建的Task实体的实例。 没有魔术方法,没有我们不记得的属性……只是我们创建的方法,保持其清洁! 现在,到控制器!

...

class TaskController extends Controller
{
    public function getIndex(EntityManagerInterface $em)
    {
        $tasks = $em->getRepository(Task::class)->findAll();

        return view('tasks', [
            'tasks' => $tasks
        ]);
    }

...

We can see something new in getIndex: the getRepository method. Put simply, a Repository is an object with a very specific responsibility: deal with the persistence layer, starting from (or arriving to) simple entities. This means that we can continue treating our entities like plain PHP object but with the benefit of an easy persistence layer management. For more information about the repository pattern, please see here.

我们可以在getIndex看到一些新内容: getRepository方法。 简而言之,存储库是一个承担非常具体责任的对象:从(或到达)简单实体处理持久层。 这意味着我们可以继续将我们的实体像普通PHP对象一样对待,但是受益于简单的持久层管理。 有关存储库模式的更多信息,请参见此处

We can build a repository for every entity we have in our software, but Doctrine makes our life simpler by giving the EntityManager a getRepository method that creates a really basic repository with the most used methods. findAll is one of these methods.

我们可以为软件中的每个实体建立一个存储库,但是Doctrine通过为EntityManager提供getRepository方法来简化我们的工作,该方法使用最常用的方法创建一个真正的基本存储库。 findAll是这些方法之一。

The result of our search is put in the $tasks variable and passed to the view.

我们的搜索结果放入$tasks变量中,并传递给视图。

A list of tasks

更改任务状态 (Change the Status of a Task)

Let us now see how to switch a task’s status from “not done” to “done” and vice-versa. Let’s create a getToggle method in the TaskController that will respond to the toggle/STATUS_ID URL.

现在让我们看看如何将任务的状态从“未完成”切换到“完成”,反之亦然。 让我们在TaskController中创建一个getToggle方法,该方法将响应toggle/STATUS_ID URL。

public function getToggle(EntityManagerInterface $em, $taskId)
{
    /* @var Task $task */
    $task = $em->getRepository(Task::class)->find($taskId);

    $task->toggleStatus();
    $newStatus = ($task->isDone()) ? 'done' : 'not done';

    $em->flush();

    return redirect('/')->with('success_message', 'Task successfully marked as ' . $newStatus);
}

We will call this specific URL from the list view. Let’s edit the table in the view:

我们将从列表视图中调用此特定URL。 让我们在视图中编辑表:

...

<td>{{ $task->getId() }}</td>
<td>{{ $task->getName() }}</td>
<td>{{ $task->getDescription() }}</td>
<td>
    @if($task->isDone())
        Done
    @else
        Not Done
    @endif
        - <a href="{{ url('toggle/' . $task->getId()) }}">Change</a>
</td>

...

We can see that we didn’t use a persist method this time around. We called the toggleStatus method of our entity and then used flush. That’s because we already saved this entity in our database – we just need to update a single, existing property value to a different one.

我们可以看到我们这次没有使用persist方法。 我们调用了实体的toggleStatus方法,然后使用flush 。 这是因为我们已经将该实体保存在数据库中–我们只需要将一个现有的属性值更新为另一个即可。

We just used our existing entity’s logic without putting anything in the controller. Tomorrow, we could change everything in the framework, yet the business logic (the toggleStatus method) would have the same effect.

我们只是使用现有实体的逻辑,而没有在控制器中放入任何东西。 明天,我们可以更改框架中的所有内容,但是业务逻辑( toggleStatus方法)将具有相同的效果。

删除任务 (Deleting a Task)

Let’s allow for the deletion of items now.

现在允许删除项目。

public function getDelete(EntityManagerInterface $em, $taskId)
{
    $task = $em->getRepository(Task::class)->find($taskId);

    $em->remove($task);
    $em->flush();

    return redirect('/')->with('success_message', 'Task successfully removed.');
}

The EntityManager automatically takes care of the task deletion process with the remove method. We can add a link in the “Operations” table column in our view to let the user delete a specific task.

EntityManager使用remove方法自动处理任务删除过程。 我们可以在视图的“操作”表列中添加链接,以使用户删除特定任务。

...

<tr>
    <td>{{ $task->getId() }}</td>
    <td>{{ $task->getName() }}</td>
    <td>{{ $task->getDescription() }}</td>
    <td>
        @if($task->isDone())
            Done
        @else
            Not Done
        @endif
            - <a href="{{ url('toggle/' . $task->getId()) }}">Change</a>
    </td>
    <td>
        <a href="{{ url('delete/' . $task->getId()) }}">Delete</a>
    </td>
</tr>

...

修改任务 (Modifying a Task)

Let’s create an edit.blade.php view.

让我们创建一个edit.blade.php视图。

@extends('master')

@section('title') Edit Task @endsection

@section('content')
    <div class="row">
        <div class="col-md-12">
            <h3>Edit Task</h3>
            <p>Use the following form to edit che chosen task.</p>

            <hr>

            <form action="{{ url('edit/' . $task->getId()) }}" method="post">
                {{ csrf_field() }}

                <p><input autofocus type="text" placeholder="Name..." name="name" class="form-control" value="{{ $task->getName() }}" /></p>
                <p><input type="text" placeholder="Description..." name="description" class="form-control" value="{{ $task->getDescription() }}" /></p>

                <hr>

                <p><button class="form-control btn btn-success">Save Task</button></p>
            </form>
        </div>
    </div>
@endsection

Now, to the controller: let’s add the getEdit and the postEdit methods.

现在,到控制器:让我们添加getEditpostEdit方法。

public function getEdit(EntityManagerInterface $em, $taskId)
{
    $task = $em->getRepository(Task::class)->find($taskId);

    return view('edit', [
        'task' => $task
    ]);
}

public function postEdit(Request $request, EntityManagerInterface $em, $taskId)
{
    /* @var Task $task */
    $task = $em->getRepository(Task::class)->find($taskId);

    $task->setName($request->get('name'));
    $task->setDescription($request->get('description'));

    $em->flush();

    return redirect('edit/' . $task->getId())->with('success_message', 'Task modified successfully.');
}

Finally, let’s add the operation in the tasks list.

最后,让我们在任务列表中添加操作。

<tr>
        <td>{{ $task->getId() }}</td>
        <td>{{ $task->getName() }}</td>
        <td>{{ $task->getDescription() }}</td>
        <td>
            @if($task->isDone())
                Done
            @else
                Not Done
            @endif
                - <a href="{{ url('toggle/' . $task->getId()) }}">Change</a>
        </td>
        <td>
            <a href="{{ url('edit/' . $task->getId()) }}">Edit</a> |
            <a href="{{ url('delete/' . $task->getId()) }}">Delete</a>
        </td>
    </tr>

Done!

做完了!

添加用户–关系 (Adding Users – Relationships)

Now, how can we define a specific link between two entities? Let’s continue our exploration of Doctrine with more examples. To do this, we will need a new entity: the User.

现在,我们如何定义两个实体之间的特定链接? 让我们通过更多示例继续对“教义”的探索。 为此,我们将需要一个新实体: User

<?php

namespace TodoList\Entities;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="users")
 */
class User
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $name;

    /**
     * @ORM\Column(type="string")
     */
    private $email;

    /**
     * @ORM\OneToMany(targetEntity="Task", mappedBy="user", cascade={"persist"})
     * @var ArrayCollection|Task[]
     */
    private $tasks;

    /**
     * User constructor.
     * @param $name
     * @param $email
     * @param $password
     */
    public function __construct($name, $email)
    {
        $this->name = $name;
        $this->email = $email;

        $this->tasks = new ArrayCollection();
    }

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return mixed
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * @return mixed
     */
    public function getTasks()
    {
        return $this->tasks;
    }

    /**
     * Assigns the $task task to the current user.
     *
     * @param Task $task
     */
    public function addTask(Task $task)
    {
        if(!$this->tasks->contains($task)) {
            $task->setUser($this);
            $this->tasks->add($task);
        }
    }
}

What’s new here? In particular, this:

这里有什么新消息? 特别是:

/**
 * @ORM\OneToMany(targetEntity="Task", mappedBy="user", cascade={"persist"})
 * @var ArrayCollection|Task[]
 */
private $tasks;

With the OneToMany annotation, we are defining a new one to many relationship between the User entity and the Task one. We are also specifying that this property will be an ArrayCollection, a structure that Doctrine uses for this purpose. We’ve also defined a couple of methods to access the current user’s tasks and add a new one.

使用OneToMany批注,我们定义了User实体和Task实体之间的新的一对多关系。 我们还指定此属性为ArrayCollection ,即Doctrine为此目的使用的结构。 我们还定义了几种方法来访问当前用户的任务并添加新任务。

/**
 * @return mixed
 */
public function getTasks()
{
    return $this->tasks;
}

/**
 * Assigns the $task task to the current user.
 *
 * @param Task $task
 */
public function addTask(Task $task)
{
    if(!$this->tasks->contains($task)) {
        $task->setUser($this);
        $this->tasks->add($task);
    }
}

The addTask method calls a setUser method that doesn’t exist yet. We will need to edit the Task class to reflect these changes. First of all, the user property…

addTask方法调用一个setUser存在的setUser方法。 我们将需要编辑Task类以反映这些更改。 首先, user属性…

/**
 * @ORM\ManyToOne(targetEntity="User", inversedBy="tasks")
 * @var User
 */
private $user;

… and then, a couple of methods to set and get the user instance.

…然后是几种设置和获取user实例的方法。

/**
 * @return User
 */
public function getUser()
{
    return $this->user;
}

/**
 * @param User $user
 */
public function setUser(User $user)
{
    $this->user = $user;
}

Obviously, before going forward, we’ll need an “updated” version of our database to be sure that everything will work fine.

显然,在继续之前,我们需要数据库的“更新”版本,以确保一切正常。

artisan doctrine:schema:update

The above command will update the schema structure according to our entities. Ok, test things out with the help of a test-rel temporary route.

上面的命令将根据我们的实体更新架构结构。 好的,借助“ test-rel临时路由进行test-rel

Route::get('test-rel', function(\Doctrine\ORM\EntityManagerInterface $em) {
    $user = new \TodoList\Entities\User(
        'Francesco',
        'francescomalatesta@live.it'
    );

    $user->addTask(new \TodoList\Entities\Task(
        'Buy milk',
        'Because it is healthy'
    ));

    $user->addTask(new \TodoList\Entities\Task(
        'Buy chocolate',
        'Because it is good'
    ));

    $em->persist($user);
    $em->flush();

    return 'Done!';
});

Here’s what we can find in our tables:

我们可以在表中找到以下内容:

Users Table
Tasks Table

It works! This is just an example, but the Doctrine documentation covers every kind of relationship you could need for your application.

有用! 这只是一个示例,但是Doctrine文档涵盖了您的应用程序可能需要的各种关系。

验证用户 (Authenticating Users)

With Eloquent, authenticating was very, very easy. By default, the chosen driver for the authentication system was “eloquent” and the User model was already there. With a little customization, the Doctrine approach can be just as simple.

使用Eloquent,进行身份验证非常非常容易。 默认情况下,为身份验证系统选择的驱动程序是“雄辩的”,并且User模型已经存在。 只要稍加定制,“教义”方法就可以很简单。

To be more precise, here’s what we will have to do:

更精确地说,这是我们要做的:

  • be sure that our User entities implement the Laravel Authenticatable interface;

    确保我们的User实体实现Laravel Authenticatable接口;

  • edit config settings in the config/auth.php file;

    config/auth.php文件中编辑配置设置;

Laravel Doctrine gives us a cool trait to handle all the required operations for the interface we want to implement. So, let’s edit the User entity.

Laravel学说给我们一个很酷的特征,它可以处理我们要实现的接口的所有必需的操作。 因此,让我们编辑User实体。

...

<?php

namespace TodoList\Entities;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;
use Illuminate\Contracts\Auth\Authenticatable;

/**
 * @ORM\Entity
 * @ORM\Table(name="users")
 */
class User implements Authenticatable
{
    use \LaravelDoctrine\ORM\Auth\Authenticatable;

...

Note: this trait will also add two new columns to our users table (password and remember_token).

注意:此特征还将向我们的users表中添加两个新列( passwordremember_token )。

Now we just have to change the config values in the config/auth.php file. More specifically, the ones in the providers array.

现在,我们只需要更改config/auth.php文件中的配置值。 更具体地说, providers数组中的那些。

...

'providers' => [
    'users' => [
        'driver' => 'doctrine',
        'model' => TodoList\User::class,
    ],

    // 'users' => [
    //     'driver' => 'database',
    //     'table' => 'users',
    // ],
],

...

Once we’re done, we can move on to the log in phase. We make a new view, login.blade.php.

完成后,我们可以进入登录阶段。 我们创建一个新视图login.blade.php

@extends('master')

@section('title') Login @endsection

@section('content')
    <div class="row">
        <div class="col-md-12">
            <h3>Login</h3>
            <p>Use the following form to login into the system.</p>

            <hr>

            <form action="{{ url('login') }}" method="post">
                {{ csrf_field() }}

                <p><input type="text" name="email" placeholder="Email..." class="form-control" /></p>
                <p><input type="password" name="password" placeholder="Password..." class="form-control" /></p>

                <hr>

                <p><button class="form-control btn btn-success">Login</button></p>
            </form>
        </div>
    </div>
@endsection

Let’s “protect” our TaskController with the auth middleware and create two routes (GET and POST) for the login procedure in the routes.php file.

让我们使用auth中间件“保护”我们的TaskController ,并在routes.php文件中为登录过程创建两条路由(GET和POST)。

<?php

Route::group(['middleware' => ['web']], function () {
    Route::get('test-user', function(\Doctrine\ORM\EntityManagerInterface $em) {
        $user = new \TodoList\Entities\User('Francesco', 'francescomalatesta@live.it');
        $user->setPassword(bcrypt('123456'));

        $em->persist($user);
        $em->flush();
    });

    Route::get('login', function() {
        return view('login');
    });

    Route::post('login', function(\Illuminate\Http\Request $request) {
        if(\Auth::attempt([
            'email' => $request->get('email'),
            'password' => $request->get('password')
        ])) {
            return redirect('/');
        }
    });

    Route::get('logout', function() {
        \Auth::logout();
        return redirect('login');
    });

    Route::group(['middleware' => ['auth']], function () {
        Route::controller('/', 'TaskController');
    });
});

Note: there’s also a test-user route to add a sample user.

注意:还有一个添加test-usertest-user路由。

用户的任务 (The User’s Tasks)

Even though we have defined a relationship, a task doesn’t have its owner yet. We can quickly fix this.

即使我们定义了关系,任务也没有它的所有者。 我们可以快速解决此问题。

Let’s add a call to the User::setUser method in the TaskController::postAdd one.

让我们在TaskController::postAdd添加对User::setUser方法的调用。

public function postAdd(Request $request, EntityManagerInterface $em)
{
    $task = new Task(
        $request->get('name'),
        $request->get('description')
    );

    $task->setUser(\Auth::user());

    $em->persist($task);
    $em->flush();

    return redirect('add')->with('success_message', 'Task added successfully!');
}

The last step is implementing a limited list in which a user can only see their own tasks. To do this, we modify the TaskController::getIndex method like this:

最后一步是实现一个受限列表,其中用户只能看到自己的任务。 为此,我们像这样修改TaskController::getIndex方法:

public function getIndex(EntityManagerInterface $em)
{
    /* @var User $user */
    $user = \Auth::user();

    $tasks = $user->getTasks();

    return view('tasks', [
        'tasks' => $tasks
    ]);
}

By calling the getTasks method, we get all the tasks for the given user. In this case, as Auth::user returns an instance of the current user, we get only the logged in user’s tasks.

通过调用getTasks方法,我们可以获取给定用户的所有任务。 在这种情况下,由于Auth::user返回当前用户的实例,因此我们仅获得登录用户的任务。

结论 (Conclusion)

Our first test application with Laravel Doctrine is complete! It wasn’t hard, was it?

我们使用Laravel Doctrine的第一个测试应用程序完成了! 不难,不是吗?

It should be noted that Laravel Doctrine has support for validation and can help us do password resets. Likewise, it supports multiple database connections for our entities just like native Laravel has for models.

应当指出,Laravel Doctrine支持验证 ,可以帮助我们进行密码重置 。 同样,它为我们的实体支持多个数据库连接 ,就像本机Laravel在模型中一样。

Last but not least, in this test application we touched on repositories. However, we were not using them at their full potential. In fact, we can extend the Doctrine EntityRepository when we need a specific set of functionalities more focused on the domain problem at hand. The related page in the documentation contains a lot of interesting information.

最后但并非最不重要的一点是,在此测试应用程序中,我们介绍了存储库 。 但是,我们并未充分利用它们的潜力。 实际上,当我们需要一组特定的功能时,可以扩展Doctrine EntityRepository ,而这些功能应更加关注手头的域问题。 文档中的相关页面包含许多有趣的信息。

Have you used Laravel Doctrine? Would you? Why / why not? Let us know in the comments!

您是否使用过Laravel主义? 你会? 为什么/为什么不呢? 让我们在评论中知道!

翻译自: https://www.sitepoint.com/laravel-doctrine-best-of-both-worlds/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值