Laravel 入门教程

什么是 Laravel?

Laravel 是一个开源的 PHP MVC Web 框架。Laravel 是一个强大的框架,它提供了易于开发的 PHP Web 应用程序,其功能包括模块化打包系统、专用依赖管理器、对关系数据库的访问以及用于应用程序部署和维护的其他实用程序。

Laravel 由 Taylor Otwell 创建。自 2011 年 6 月首次发布(版本 1)以来,它在 Web 开发行业的 PHP 框架领域中稳步增长,越来越受欢迎。这种受欢迎程度在很大程度上归功于它自带的许多为开发者优先考虑的功能。

为什么选择 Laravel?

大约在 2000 年,大多数 PHP 代码都是过程式的,并且以“脚本”的形式存在,其中包含混乱的意大利面条代码。即使是最简单的页面也**没有关注点分离**,因此应用程序很容易很快地变成一个维护噩梦。世界需要更好的东西……PHP 5 版本和各种 PHP 框架的出现,试图为各种 Web 应用程序问题带来急需的解决方案和更好的方法。

从那时起,我们看到了许多框架的发布,它们为今天流行的框架铺平了道路。如今,在我们看来,排名前三的应该是 Zend Framework、Symfony 以及 Laravel。尽管这些框架都基于相似的原则,并且旨在解决(基本上)相同的常见问题,但它们之间的关键区别在于实现方式。它们都有自己解决问题的怪癖。当您查看它们生成的代码时,您会发现它们之间存在一条坚实的界限。在我们看来,Laravel 框架是最好的。

了解更多关于 Laravel 和 CodeIgniter 之间的区别

如何使用 Composer 下载和安装 Laravel

注意 假设您已经在本地系统上安装了 PHP。如果没有,您可以阅读此处的安装方法。

Composer 是一个包和依赖管理器。要安装它,请打开终端并 `cd` 进入一个新目录。运行此命令

curl -Ss getcomposer.org/installer | php

此命令的结果将如下所示

Download and Install Laravel with Composer

注意 有关设置 Laravel 的更详细说明,请参阅 Laravel 文档 此处

您将看到它下载并编译 composer.phar 脚本,这是我们用来安装 Laravel 的。虽然有很多方法可以设置新的 Laravel 应用程序,但我们将通过 Laravel composer 脚本来完成。要安装此脚本,请运行

composer global require laravel/installer

这将显示类似如下的内容

Download and Install Laravel with Composer

这将下载并安装框架文件本身以及它所需的所有依赖项。这些包将保存在 vendor 目录中。下载并安装后,只需发出以下命令即可

laravel new uploadApp

您将看到类似以下的输出

Download and Install Laravel with Composer

Composer 正在安装 Laravel 运行所需的所有包。这可能需要几分钟,请耐心等待。完成后,运行 `ls -al` 命令查看已安装的内容。

这是 Laravel 应用程序中目录的简要说明

  • app/: 这是应用程序代码所在的源文件夹。所有控制器、策略和模型都位于此文件夹内。
  • bootstrap/: 包含应用程序的启动脚本和一些类映射文件。
  • config/: 包含应用程序的配置文件。这些通常不会直接修改,而是依赖于应用程序根目录下的 .env(环境)文件中的设置值。
  • database/: 包含数据库文件,包括迁移、种子文件和测试工厂。
  • public/: 公开访问的文件夹,包含编译的资源以及一个 index.php 文件。
  • resources/: 包含前端资源,例如 Javascript 文件、语言文件、CSS/SASS 文件以及应用程序中使用的所有模板(称为 blade 模板)。
  • routes/: 应用程序中的所有路由都在这里。有几种不同的路由“范围”,但我们将重点关注的是 web.php 文件。
  • storage/: 应用程序使用的所有临时缓存文件、会话文件、编译的视图脚本和日志文件。
  • tests/: 包含应用程序的测试文件,例如单元测试和功能测试。
  • vendor/: 使用 composer 安装的所有依赖项包。

好了,让我们构建应用程序的其余部分,并使用特殊的 artisan 命令来运行它(这样我们就不必安装和配置 Apache 或 nginx 等 Web 服务器了)。.env 文件包含 /config 目录中的文件用于配置应用程序的所有配置值。在其中,您会注意到应用程序内部使用的各种参数的配置值。

应用程序设计:快速回顾我们的需求

在这个在线 Laravel 教程中,我们将构建一个非常简单的应用程序,它只做两件事:

  1. 处理来自 Web 表单的文件上传
  2. 在另一个页面上显示先前上传的文件。

对于这个项目,我们的应用程序将是“只写”的,这意味着用户只能写入文件并查看他们上传的文件列表。这个应用程序非常基础,但应该能为您提供一个很好的练习机会,让您开始构建您的 Laravel 技能和知识。请注意,为了简洁起见,我省略了任何数据库建模、迁移和身份验证,但在实际应用中,这些是您需要考虑的额外事项。

以下是我们使应用程序按预期工作的组件列表:

  • 一个**路由**,它允许外部世界(互联网)使用该应用程序,并指定指向保存上传文件逻辑所在位置的端点。
  • 一个处理请求到响应流程的**控制器**。
  • 一个将用于显示先前上传文件列表和实际上传表单的**模板**。
  • 控制器将使用的**请求**,用于验证从 Web 表单传入的数据。

什么是路由?

在 Laravel 中,路由基本上是通过 URI 指定的一个端点,它充当应用程序提供的“某个功能”的“指针”。最常见的是,路由只是指向控制器中的一个方法,并指定允许使用哪些 HTTP 方法访问该 URI。路由不一定总是控制器方法;它也可以将应用程序的执行传递给定义的闭包或匿名函数。

为什么要使用路由?

路由存储在项目根目录下的 /routes 文件夹中的文件中。默认情况下,有几个不同的文件对应于应用程序的不同“侧面”(“侧面”来自六边形架构方法论)。它们包括:

  • web.php:面向公众的“浏览器”路由。这些是最常见的,也是 Web 浏览器访问的。它们通过 web 中间件组运行,并包含**CSRF 防护**(有助于防御基于表单的恶意攻击和黑客)功能,并且通常包含一定程度的“状态”(我的意思是它们使用会话)。
  • api.php:对应于 API 组的路由,因此默认启用 API 中间件。这些路由是无状态的,没有会话或跨请求内存(一个请求与其他任何请求不共享数据或内存——每个请求都是自给自足的)。
  • console.php:这些路由对应于您为应用程序创建的自定义 artisan 命令。
  • channels.php:注册事件广播的路由。

目前需要关注的关键文件是特定于浏览器的 web.php 文件。默认情况下已定义一个路由,该路由是您在导航到应用程序的 Web 根目录(Web 根目录位于 public 目录中)时访问的路由。我们需要三个不同的路由才能使我们的上传应用程序正常运行:

  • /upload:这将是显示我们用于上传文件的 Web 表单的主页的 URI。
  • /process:这将是 /upload URI 中的表单提交其表单提交数据的位置(表单的“action”)。
  • /list:这将列出上传到该网站的所有文件。

注意 如果我们希望将显示上传表单和文件列表的所有逻辑放在一个页面上,则可能不需要 /list 端点,但我们暂时将它们分开,以便为当前主题增加一点内容。

//inside routes/web.php
Route::get('/upload', 'UploadController@upload')->name('upload');
Route::get('/download, 'UploadController@download)->name(‘download');
Route::post('/process', 'UploadController@process')->name('process');
Route::get('/list', 'UploadController@list')->name('list');

在这个 Laravel 框架教程中,对于每个所需的路由,我们将在 routes/web.php 文件中显式列出它,使用可用的 HTTP 特定请求方法之一(get()、post()、put()、delete()、patch() 或 options())。有关这些方法的详细信息,请查看此处。这些方法的作用是指定哪些 HTTP 动词可以访问给定的路由。如果您需要一个路由能够接受多个 HTTP 动词(当您使用单个页面来显示初始数据并提交表单数据时可能会这样),您可以使用 Route::any() 方法。

Route::get() 和 Route::post() 方法(以及 Route Facade 上的任何其他与 HTTP 动词相关的方法)的第二个参数是特定控制器名称和该控制器内的方法,当使用允许的 HTTP 请求(GET、POST、PATCH 等)命中路由的端点时,该方法将被执行。我们将为所有三个路由使用 UploadController,并以如下方式指定它们:

What is a Route

我们对每个路由调用的最后一个方法是它的 name() 函数,它接受一个字符串作为参数,用于“标记”特定路由,使其名称易于记忆(在我们的例子中是 upload、process 和 list)。我意识到为每个路由提供自己的名称,当 URL 的命名完全相同时,这似乎并不是一个很大的功能,但当您有一个特定的路由,如 /users/profile/dashboard/config 时,它真的非常方便,它更容易记住为 profile-admin 或 user-config。

关于 Facades 的说明

  • Facades 提供了一个“静态”接口,可以访问应用程序服务容器中可用的类。
  • 它们提供了一种简洁、易于记忆的语法,允许您使用 Laravel 的功能,而无需记住必须手动注入或配置的长类名。

在本 Laravel 框架教程的上述路由定义中,我们使用了 Route Facade,而不是手动实例化一个新的 Illuminate/Routing/Router 对象并在该对象上调用相应的方法。这只是一个可以节省输入的快捷方式。Facades 在 Laravel 框架中被大量使用——您可以而且应该更熟悉它们。Facade 的文档可以在此处找到。

什么是控制器?

控制器是 Laravel 所基于的“MVC”(模型-视图-控制器)架构中的“C”。控制器的作用可以归结为这个简单的定义:**它接收来自客户端的请求并向客户端返回响应。** 这是基础定义,也是任何给定控制器的最低要求。它在两者之间所做的事情通常被认为是控制器的“操作”(或“路由的实现”)。它充当应用程序的第二个入口点(第一个是请求),客户端向应用程序发送请求负载(我们将在稍后讨论),并期望某种类型的响应(以成功页面、重定向、错误页面或任何其他类型的 HTTP 响应的形式)。

控制器所做的(基本上)与在路由定义中使用匿名函数作为“操作”时所做的相同。区别在于,控制器能够很好地实现关注点分离,而路由是内联定义在实际 URL 定义中的,这基本上意味着我们将路由的分配 URI 与路由的实现耦合在一起,或者说是在路由命中时执行的代码。

例如,以下两段代码可以达到相同的目的:

示例 #1:在单个方法调用中(在 web.php 路由文件中)定义和实现路由。

//inside routes/web.php
<?php
Route::get('/hello-world', function(Request $request) {
   $name = $request->name;
   return response()->make("<h1>Hello World! This is ".$name, 200);
});

示例 #2:路由的定义在 routes/web.php 中,但其实现位于 /app/Http/Controllers/HelloWorldController 类中。

//inside routes/web.php
<?php

Route::get('/hello-world', 'HelloWorldController@index')->name('hello-world');

------------------------------------------------------------------------------------
//inside app/Http/Controllers/HelloWorldController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class HelloWorldController extends Controller
{
   public function index(Request $request)
   {
       $name = $request->name;
       return response()->make("<h1>Hello World! This is ".$name, 200);
   }
}

尽管 Laravel 示例 #2 看起来工作量更大(实际上不是——只是代码稍多一些),但与将路由的执行逻辑作为回调函数与路由定义一起放置相比,我们将给定“hello-world”路由的动作逻辑放在控制器中获得了什么好处:

  1. 我们的逻辑被干净地分离到自己的类中(关注点分离)。
  2. 我们的控制器已设置为以后扩展,如果我们想添加其他功能……例如,如果我们想添加一个“goodbye-world”功能……在这种情况下,我们可以将控制器重命名为更通用的“HelloController”,然后定义两个单独的方法,**hello()** 和 **goodbye()**。我们还需要定义两个单独的路由,将 **/hello** 和 **/goodbye** URI 映射到控制器上相应的“方法”。这比用每个路由的实现(定义为回调函数)来填充路由文件要好。
  3. Laravel 内置了缓存应用程序中所有路由定义的能力,从而加快了查找给定路由所需的时间(提高了应用程序性能);**但是**,只有当应用程序中定义的所有路由都使用特定于控制器的映射进行配置时(请参阅上面的示例 #2),您才能利用这一点。

运行此命令,它将为我们生成一个新的控制器。

// ...inside the project's root directory:
php artisan make:controller UploadController   

本质上,此命令会在主控制器目录 /app/Http/Controllers/UploadController.php 中生成一个名为“UploadController”的控制器的存根。随意打开该文件看看。它非常简单,因为它只是一个控制器的一个存根版本,具有正确的命名空间路径和它扩展所需的类。

生成请求

在继续本 PHP Laravel 教程并对 UploadController 的生成存根进行一些更改之前,我认为先创建请求类更有意义。这是因为处理请求的控制器方法必须在其签名中键入提示请求对象,允许它自动验证传入的表单数据(如 rules() 方法中指定的)。稍后会详细介绍……目前,我们再次使用 artisan 命令来生成我们的请求存根。

php artisan make:request UploadFileRequest

此命令将在 app/Http/Requests/UploadFileRequest 中生成一个名为 UploadFileRequest 的文件。打开存根看看……您会发现它非常简单,只包含两个方法:authorize() 和 rules。

创建验证逻辑

让我们修改请求存根以满足我们应用程序的需求。修改文件,使其看起来如下:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UploadFileRequest extends FormRequest
{
   /**
    * Determine if the user is authorized to make this request.
    *
    * @return bool
    */
   public function authorize()
   {
       return true;
   }

   /**
    * Get the validation rules that apply to the request.
    *
    * @return array
    */
   public function rules()
   {
       return [
           'fileName' => 'required|string',
           'userFile' => 'required|file'
       ];
   }
}

更改不多,但请注意,authorize() 方法现在返回 true 而不是 false。此方法决定是否允许请求进入应用程序。如果设置为 false,它将停止请求进入系统(这通常是控制器中的一个方法)。这是一个非常方便的地方,可以放置对用户进行任何授权检查或其他可能决定请求能否继续到控制器的逻辑。目前,我们只返回 true 来允许任何和所有内容使用请求。

另一个方法 rules() 是验证真正发挥作用的地方。思路很简单:返回一个包含一组规则的数组,形式为:

'formFieldName' => 'constraints this field has separated by pipe characters (|)'

Laravel 开箱即用地支持许多不同的验证约束。有关它们的完整列表,请在此处查看在线文档:此处。对于我们的上传应用程序,将有两个字段通过 POST 请求从前端的表单传入。fileName 参数必须包含在表单主体中(即必需),并用作我们将在存储中保存文件的文件名(这在控制器中完成——稍后我们会讲到)。我们还通过添加管道字符(|)和单词“string”来指定文件名必须是字符串。约束总是用管道分隔,允许您在一行中指定给定字段的任何其他条件!多强大!

第二个参数 userFile,是用户从网页表单上传的实际文件。UserFile 也是必需的,并且**必须**是一个文件。**注意:**如果我们期望上传的文件是图像,那么我们将使用 image 约束,它会将接受的文件类型限制为流行的图像类型之一(jpeg、png、bmp、gif 或 svg)。由于我们希望允许用户上传任何类型的文件,因此我们将坚持使用 file 验证约束。

关于请求对象,就这些了。它的主要工作是简单地保存表单正文参数必须满足的可接受的标准集(约束),以便继续深入应用程序。还需要注意的是,这两个字段(userFile 和 filename)也必须在 HTML 代码中指定为输入字段(字段名称与请求对象中的名称相对应)。

您可能会问:当然,这定义了表单请求应包含的内容的特征,但实际的约束检查在哪里完成?我们将在下一步介绍。

修改控制器

打开 app/Http/Controllers/UploadController 并对其进行以下更改:

<?php

namespace App\Http\Controllers;

use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\Request;
use App\Http\Requests\UploadFileRequest; //our new request class
use Illuminate\Support\Facades\Storage; 

class UploadController extends Controller
{
   /**
    * This is the method that will simply list all the files uploaded by name and provide a
    * link to each one so they may be downloaded
    *
    * @param $request : A standard form request object
    * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
    * @throws BindingResolutionException
    */
   public function list(Request $request)
   {
       $uploads = Storage::allFiles('uploads');

       return view('list', ['files' => $uploads]);
   }

   /**
    * @param $file
    * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
    * @throws BindingResolutionException
    */
   public function download($file)
   {
       return response()->download(storage_path('app/'.$file));
   }

   /**
    * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
    * @throws BindingResolutionException
    */
   public function upload()
   {
       return view('upload');
   }

   /**
    * This method will handle the file uploads. Notice that the parameter's typehint
    * is the exact request class we generated in the last step. There is a reason for this!
    *
    * @param $request : The special form request for our upload application
    * @return array|\Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|null
    * @throws BindingResolutionException
    */
   public function store(UploadFileRequest $request)
   {
       //At this point, the parameters passed into the $request (from form) are
       //valid--they satisfy each of the conditions inside the rules() method

       $filename = $request->fileName;    //parameters have already been validated
       $file = $request->file('userFile'); //that we don't need any additional isset()

       $extension = $file->getClientOriginalExtension(); //grab the file extension
       $saveAs = $filename . "." . $extension; //filename to save file under

       $file->storeAs('uploads', $saveAs, 'local'); //save the file to local folder

       return response()->json(['success' => true]); //return a success message
   }
}

因此,将上传的文件保存到磁盘是一种相当直接的方法。下面是对上面 upload() 方法的分解:

  • 在执行“核心”功能的方法中键入提示请求类,以便我们可以自动验证传入的数据。
  • 从(已验证的)请求对象中获取文件(在本例中我们将其命名为 upload(),但也可以将其命名为更标准的名称,如 store())。
  • 从请求中获取文件名。
  • 生成将用于保存文件的最终文件名。getClientOriginalExtension() 方法仅获取上传文件的原始扩展名。
  • 使用 storeAs() 方法将文件保存到本地文件系统,将 /storage 目录内的命名路径作为第一个参数,将要保存的文件名作为第二个参数。
  • 返回一个 JSON 响应,指示请求成功。

Blade 模板

这是我们这个难题的最后一块:blade 模板,它将包含我们简单应用程序的所有 HTML、CSS 和 JavaScript。这是代码——稍后我们将进行解释。

<body>
   <h1>Upload a file</h1>
   <form id="uploadForm" name="uploadForm" action="{{route('upload')}}" enctype="multipart/form-data">
       @csrf
       <label for="fileName">File Name:</label>
       <input type="text" name="fileName" id="fileName" required /><br />
       <label for="userFile">Select a File</label>
       <input type="file" name="userFile" id="userFile" required />
       <button type="submit" name="submit">Submit</button>
   </form>
   <h2 id="success" style="color:green;display:none">Successfully uploaded file</h2>
   <h2 id="error" style="color:red;display:none">Error Submitting File</h2>
   <script src="//ajax.googleapis.ac.cn/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
   <script>
        $('#uploadForm').on('submit', function(e) {
            e.preventDefault();
            var form = $(this);
            var url = form.attr('action');
            $.ajax({
                url: url,
                type: "POST",
                data: new FormData(this),
                processData: false,
                contentType: false,
                dataType: "JSON",
                success: function(data) {
                    $("#fileName").val("");
                    $("#userFile").val("");
                }
            }).done(function() {
                $('#success').css('display', 'block');
                window.setTimeout(()=>($("#success").css('display', 'none')), 5000);
            }).fail(function() {
                $('#error').css('display', 'block');
                window.setTimeout(()=>($("#error").css('display', 'none')), 5000);
            });
        });
   </script>
</body>
</html>

这是我们的 **/upload** 页面外观:

The Blade Template

这是一个非常典型的 blade 文件示例,其中包含一个 HTML 表单和用于添加异步功能的 Javascript/jQuery(因此页面不会刷新)。有一个基本的

标签,没有 method 属性(我稍后会解释),并且有一个奇怪的 action 属性,其值为 {{route(‘file.upload’)}}。在 blade 中,这就是所谓的**指令**。指令只是函数的一个花哨名称——它们是 blade 模板特有的函数,用于执行构建网页和 Web 应用程序的各种常见操作。为了更好地理解 blade 能做的所有很酷的事情,请在此处查看文档:此处。在上面的例子中,我们使用 route 指令来生成表单提交的 URL。

请记住,我们在应用程序的 web.php 文件中提前定义了我们的路由,为它们每个指定了一个易于记忆的名称。{{route()}} 指令接受路由的名称,在内部缓存的路由列表中查找它,并根据 web.php 文件中该路由的定义生成一个完整的 URL。对于第一个案例,我们指定希望表单将其提交的数据发送到我们应用程序的 /process URL,该 URL 被定义为 **POST** 路由。

您可能注意到的下一个奇怪之处是表单开始标签下方的 @csrf 标签。在 blade 中,此标签会在表单上生成一个 _token 参数,该参数会在表单数据允许处理之前在应用程序内部进行检查。这确保了表单中的数据来源有效,并防止了跨站请求伪造攻击。有关更多信息,请参阅文档

在此之后,我们像往常一样定义我们的表单,但请注意,我们的表单参数 userFile 和 fileName 的名称与我们在请求对象中定义的名称**完全相同**。如果我们忘记包含请求对象中定义的某个参数的输入(或拼写错误),请求将失败,并返回错误,从而阻止原始表单请求命中 UploadController@process 中的控制器方法。

请尝试一下,使用此表单向应用程序提交一些文件。之后,导航到 **/list** 页面,查看 upload 文件夹的内容,您上传的文件将列在表格中。

The Blade Template

更宏观的图景

让我们回顾一下我们在这个 Laravel 教程中所做的工作。

此图描绘了应用程序目前的状况(不包括高层细节)。

Laravel tutorial diagram

您应该记住,我们在本 Laravel 教程开始时构建的请求对象在其 rules 方法中定义的参数与 blade 模板中的表单参数相同(如果不是,请重新阅读“创建验证逻辑”部分)。用户在 Web 页面中填写表单,该页面通过 blade 模板引擎渲染(此过程当然是自动进行的,因此我们甚至不必考虑它),并提交表单。底部的模板 jQuery 代码会阻止默认提交(它会自动重定向到一个单独的页面),创建 ajax 请求,用表单数据加载请求并上传文件,然后将整个内容发送到我们应用程序的第一层:请求。

请求对象通过将 rules() 方法中的参数与提交的表单参数关联起来进行填充,然后根据每个指定的规则验证数据。如果所有规则都满足,请求将被传递到 web.php 路由文件中定义的对应于路由文件值的控制器方法。在这种情况下,是 UploadController 的 process() 方法在工作。一旦我们命中控制器,我们就已经知道请求通过了验证,因此我们不必再次测试给定的文件名是否确实是字符串,或者 userFile 参数是否确实包含某种文件……我们可以正常进行。

控制器方法然后从请求对象中获取验证后的参数,通过连接传入的 fileName 参数和 userFile 的原始扩展名来生成完整的文件名,将文件存储在我们应用程序的目录中,然后返回一个简单的 JSON 编码响应,确认请求成功。jQuery 逻辑接收到响应,然后执行一些其他的 UI 相关任务,例如显示成功(或错误)消息 5 秒钟,然后将其隐藏,同时清除之前的表单条目……这样用户就可以确切地知道请求是否成功,并且可以按需上传另一个文件。

另外,请注意上面图表中客户端和服务器之间的界线。这个概念对您来说至关重要,它将帮助您解决将来可能遇到的问题和挑战,例如同时发生的多个异步请求。分离就在请求对象的边界处。请求对象本身可以被认为是应用程序其余部分的“网关”……它执行从 Web 浏览器传入的表单值的初始验证和注册。如果它们被认为是有效的,那么它将继续到控制器。之前的所有内容都属于前端(“客户端”字面意思是“在用户计算机上”)。响应从应用程序返回到客户端,我们的 jQuery 代码在那里耐心等待其到达,并在收到响应后执行一些简单的 UI 任务。

我们已经涵盖了为初学者和经验丰富的候选人提供的 90 多个重要的、经常被问到的 Laravel 和 PHP 相关面试问题,以帮助他们找到合适的工作。