Laravel 初学者教程

什么是 Laravel?

Laravel 是适用于 PHP 的开源 Web MVC 框架。Laravel 是一个强大的框架,它提供诸如模块化打包系统(带有专用依赖项管理器)、关系数据库访问和其他用于应用程序部署和维护的实用程序等功能,可轻松开发 PHP Web 应用程序。

Laravel 由 Taylor Otwell 创建。自 2011 年 1 月首次发布(版本 XNUMX)以来,它在 Web 开发行业的 PHP 框架领域逐渐流行起来。这种流行很大程度上可以归因于它自带的许多以开发人员为先的功能。

为什么选择 Laravel?

2000 年左右,大多数 PHP代码 是程序化的,可以以“脚本”的形式找到,这些脚本会包含一团乱糟糟的意大利面条式代码。即使是最简单的页面也没有 关注点分离,因此应用程序很容易迅速发展成为维护噩梦。世界需要更好的东西……PHP 5 和各种 PHP 框架试图为各种 Web 应用程序问题带来一些急需的解决方案和更好的解决方案。

从那时起,我们看到许多框架的发布为当今流行的框架铺平了道路。如今,我们认为排名前三的框架是 Zend Framework、Symfony 和 Laravel。尽管这些框架都基于类似的原则,并且旨在解决(基本上)相同的常见问题,但它们的主要区别在于它们的实现。它们各自都有解决问题的怪癖。当你查看它们各自生成的代码时,你会发现它们之间有一条非常明显的界线。在我们看来,Laravel 框架是最好的。

进一步了解 Laravel 和 CodeIgniter 之间的区别

如何使用 Composer 下载和安装 Laravel

注意 假设您已经在本地系统上安装了 PHP。如果没有,您可以阅读如何安装它 开始

Composer 既是包管理器又是依赖管理器。要安装它,请打开终端并进入新目录。运行以下命令:

curl -Ss getcomposer.org/installer | php

此命令的结果将如下所示:

使用 Composer 下载并安装 Laravel

注意: 有关设置 Laravel 的更多详细说明,请参阅 Laravel 文档 开始.

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

composer global require laravel/installer

看起来像这样:

使用 Composer 下载并安装 Laravel

这将下载并安装所有框架文件以及所需的所有依赖项。软件包将保存在供应商目录中。下载并安装后,只需发出以下命令即可:

laravel new uploadApp

您将看到类似以下输出:

使用 Composer 下载并安装 Laravel

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

以下是常见 Laravel 应用程序中目录的简要分类:

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

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

应用程序设计:简要概述我们的要求

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

  1. 处理来自网络表单的文件上传
  2. 在不同的页面上显示先前上传的文件。

对于这个项目,我们的应用程序将是只写的,这意味着用户只能写入文件并查看他们已上传的文件列表。这个应用程序非常基础,但应该可以作为您开始构建 Laravel 技能和知识的良好实践。请注意,为了简洁起见,我排除了任何数据库建模、迁移和身份验证,但在实际应用程序中,这些都是您需要考虑的其他事项。

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

  • A 路线 这将允许外界(互联网)使用该应用程序,并指定指向保存上传文件的逻辑的端点
  • A 调节器 处理请求到响应流程
  • A 模板 用于显示之前上传的文件列表和实际的上传表单本身
  • A 请求 控制器将使用它来验证从 Web 表单传入的数据

什么是路线?

Laravel 中的路由基本上是由 URI 指定的端点,充当指向应用程序提供的某些功能的“指针”。最常见的是,路由只是指向控制器上的方法,还指示哪些 HTTP 方法能够命中该 URI。路由也并不总是意味着控制器方法;它也可以只是将应用程序的执行传递给定义的闭包或匿名函数。

为什么要使用路线?

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

  • web.php 面向公众的“浏览器”路由。这些路由最常见,并且是 Web 浏览器所访问的路由。它们贯穿 Web 中间件组,还包含以下设施: csrf 保护 (这有助于抵御基于表单的恶意攻击和黑客攻击)并且通常包含一定程度的“状态”(我的意思是它们利用会话)
  • api.php 与 API 组对应的路由,因此默认启用 API 中间件。这些路由是无状态的,没有会话或跨请求内存(一个请求不会与任何其他请求共享数据或内存 - 每个请求都是自我封装的)。
  • console.php 这些路由对应于你为应用创建的自定义 artisan 命令
  • channels.php 注册事件广播的路由

此时要关注的关键文件是特定于浏览器的文件 web.php 。默认情况下已定义一条路由,即导航到应用程序的 Web 根目录时单击的路由(Web 根目录位于公共目录中)。我们需要三种不同的路由才能使上传应用程序正常运行:

  • /upload 这将是显示用于上传文件的 Web 表单的主页的 URI。
  • /process 这将是位于 /upload URI 的表单将其表单提交的数据发布到的位置(表单的“操作”)
  • /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 框架教程中,对于每个所需的路由,我们将使用可用的 HTTP 特定请求方法之一(get()、post()、put()、delete()、patch() 或 options())在路由文件 web.php 中明确列出它。有关每个方法的细分,请查看 Free Introduction out。这些方法的作用是指定允许哪些 HTTP 动词访问给定的路由。如果您需要一个能够接受多个 HTTP 动词的路由(如果您使用单个页面来显示初始数据和发布提交的表单数据,则可能出现这种情况),您可以使用 Route::any() 方法。

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

什么是路线

我们在每个路由上调用的最后一个方法是它的 name() 函数,它接受单个字符串作为参数,并用于或多或少地用容易记住的名称“标记”特定路由(在我们的例子中是上传、处理和列表)。我意识到当 URL 名称完全相同时,为每个路由赋予自己的名称似乎并不是一个很棒的功能,但当您有像 /users/profile/dashboard/config 这样的特定路由时,它确实很方便,因为更容易记住为 profile-admin 或 user-config。

关于 Facades 的说明:

  • 外观为应用程序服务容器中可用的类提供了“静态”接口。”
  • 它们提供了简洁、易记的语法,让您可以使用 Laravel 的功能,而无需记住必须手动注入或配置的长类名。

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

什么是控制器?

控制器是“MVC”(模型-视图-控制器)架构中的“C”,Laravel 就是基于此架构的。控制器的工作可以归结为以下简单定义: 它接收来自客户端的请求并向客户端返回响应。 这是最基本的定义,也是任何给定控制器的最低要求。它在这两件事之间所做的通常被视为控制器的“操作”(或“路由的实现”)。它充当应用程序的第二个入口点(第一个是请求),客户端将请求负载(我们将在下文中介绍)发送到应用程序,并期望某种类型的响应(以成功页面、重定向、错误页面或任何其他类型的 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. 如果我们需要为控制器添加额外的功能,我们可以将其设置为稍后进行扩展……比如说,我们想添加一个“再见世界”功能……在这种情况下,我们可以将控制器重命名为更通用的“HelloController”,然后定义两个单独的方法, 你好() 以及 再见()。我们还需要定义两条单独的路线来映射 /你好 以及 / 再见 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 也是必需的, 必须 是一个文件。 注意: 如果我们期望上传的文件是图像,那么我们将使用图像约束,这将限制接受的文件类型为流行的图像类型之一(jpeg、png、bmp、gif 或 svg)。由于我们希望允许用户上传任何类型的文件,我们将坚持使用文件验证约束。

这就是请求对象的全部内容。它的主要工作就是简单地保存一组可接受的标准(约束),表单的主体参数必须满足这些标准(约束)才能更深入地进入应用程序。另外要注意的是,这两个字段(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.com/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>

以下是我们的 / 上传 页面看起来像:

Blade 模板

这是一个非常典型的 blade 文件示例,其中包含 HTML 表单和 javascript/jQuery,用于添加异步功能(因此页面不会刷新)。有一个基本的标签没有 method 属性(稍后我将解释),并且带有一个奇怪的 action 属性,其值为 {{route('file.upload')}}。在 blade 中,这就是所谓的 指令。 指令只是函数的别名——它们是 blade 模板特有的函数,用于执行构建网页和 Web 应用程序时常见的不同操作。为了更好地了解 blade 可以执行的所有酷炫功能,请查看文档 开始。在上述情况下,我们使用路由指令来为表单提交生成一个 URL。

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

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

在此之后,我们将表单定义为正常形式,但请注意,表单参数的名称 userFile 和 fileName 是 相同的 如我们的请求对象中定义的那样。如果我们忘记为请求对象中定义的给定参数添加输入(或拼写错误),请求将失败并返回错误,从而阻止原始表单请求命中位于 UploadController@process 的控制器方法。

继续尝试,使用此表单向应用程序提交一些文件。然后,导航到 /列表 页面查看上传文件夹的内容,其中表格中列出了您上传的文件:

Blade 模板

大局观

让我们回顾一下,看看我们在这个 Laravel 教程中做了什么。

这张图描述了应用程序的当前状态(不包括高层细节):

Laravel 教程图

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

通过将 rules() 方法中的参数与提交的表单参数关联起来,可以填充请求对象,然后根据每个指定的规则验证数据。如果满足所有规则,请求将传递到与路由文件 web.php 中定义的值相对应的任何控制器方法。在本例中,UploadController 的 process() 方法负责执行工作。一旦我们进入控制器,我们就知道请求已通过验证,因此我们不必重新测试给定的文件名是否实际上是字符串,或者 userFile 参数是否实际包含某种类型的文件……我们可以照常继续。

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

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

我们涵盖了近 90 多个重要的常见问题 Laravel 和 PHP 相关面试题 帮助应届毕业生和有经验的求职者找到合适的工作。

总结一下这篇文章: