laravel 详解,初学者,入门


什么是 Laravel?

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

Laravel 是由 Taylor Otwell 创建的。自 2011 年 6 月首次发布(第 1 版)以来,它在 Web 开发行业的 PHP 框架领域越来越受欢迎。这种受欢迎程度在很大程度上可以归因于它附带的许多开发人员优先考虑的功能。

为什么选择 Laravel?

大约在 2000 年,大多数 PHP 代码都是程序性的,并且可以以“脚本”的形式找到,这些代码会有一堆乱七八糟的意大利面条代码。即使是最简单的页面也没有关注点分离,因此应用程序很容易很快变成维护的噩梦。世界需要更好的东西……进入 PHP 版本 5 和各种 PHP 框架,试图为各种 Web 应用程序问题带来一些急需的解决方案和更好的解决方案。

从那时起,我们看到了许多框架的发布,这些框架将为当今存在和使用的流行框架铺平道路。今天,前三名(在我们看来)是 Zend Framework、Symfony,当然还有 Laravel。尽管这些框架中的每一个都建立在相似的原则之上,并且旨在解决(基本上)相同的常见问题,但它们的主要区别在于它们的实现。对于如何解决问题,他们每个人都有自己的怪癖。当您查看它们各自生成的代码时,您会发现它们之间有一条非常实线分开。在我们看来,Laravel 框架是最好的。

在这个 Laravel 初学者教程中,你将学习 Laravel 基础知识,例如:

如何使用 Composer 下载和安装 Laravel

注意假设您已经在本地系统上安装了 PHP 的副本。

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

curl -Ss getcomposer.org/installer | php

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

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

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

composer global 需要 laravel/installer

看起来像这样:

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

laravel 新上传应用

您将看到类似于以下输出的内容:

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

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

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

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

应用程序设计:快速了解我们的要求

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

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

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

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

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

什么是Route?

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

为什么要使用Route?

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

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

此时要关注的关键文件是特定于浏览器的文件 web.php 。默认情况下已经定义了一个路由,这是您在导航到应用程序的 Web 根目录时点击的那个(Web 根目录位于公共目录中)。我们将需要三个不同的路由来让我们的上传应用程序运行:

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

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

//routes/web.php
Route::get('/upload', '[email protected]')->name('upload');
Route::get('/download, '[email protected])->name('download');
Route::post('/process', '[email protected]')->name('process');
Route::get('/list', '[email protected]')->name('list');

在这个 Laravel 框架教程中,对于每个所需的路由,我们将使用可用的 HTTP 特定请求方法之一(get()、post()、put()、delete()、patch() 或 options())。有关其中每一个的细分,请查看内容。这些方法所做的是指定允许哪些 HTTP 动词访问该给定路由。如果您需要一个能够接受多个 HTTP 动词的路由(如果您使用单个页面来显示初始数据和提交提交的表单数据,则可能是这种情况),您可以使用 Route::any( ) 方法。

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

我们在每个路由上调用的最后一个方法是它的 name() 函数,它接受单个字符串作为参数,并用于或多或少地“标记”具有易于记忆的名称的特定路由(在我们的例子中,上传、处理和列出)。我意识到当 URL 被命名为完全相同时,给每条路由赋予自己的名称似乎并不是一个很好的功能,但是当你有一个特定的路由时,它真的很方便,比如 /users/profile/dashboard/config,作为配置文件管理员或用户配置更容易记住。

关于Facades的注释:

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

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

什么是控制器?

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

控制器做(基本上)与路由定义相同的事情,当路由被命中时,将匿名函数设置为“动作”。不同之处在于控制器很好地支持关注点分离,而路由被定义为内联到实际的 url 定义,这基本上意味着我们将路由分配的 URI 与路由的实现或当该路由执行时执行的代码耦合打。

例如,以下两段代码将实现相同的目的:

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

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

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

//inside routes/web.php
<?php

Route::get('/hello-world', '[email protected]')->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”然后定义两个独立的方法,hello()goodbye()。我们还需要定义两个单独的路由,将/hello/goodbye URI 映射到控制器上的相应方法。与使用每个路由的实现定义为回调函数来增加路由文件相比,这是可取的。
  3. Laravel 具有缓存应用程序中所有路由定义的内置功能,以便加快查找给定路由所需的时间(提高应用程序性能);但是,只有在应用程序中定义的所有路由都使用特定于控制器的映射进行配置时,您才能利用这一点(参见上面的示例 #2)

让我们运行这个将为我们生成一个新控制器的命令。

// ...在项目的根目录中:
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' => '约束此字段由竖线字符 (|) 分隔'

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

第二个参数 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; 
use Illuminate\Support\Facades\Storage; 

class UploadController extends Controller
{
   
   public function list(Request $request)
   {
       $uploads = Storage::allFiles('uploads');

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

  
   public function download($file)
   {
       return response()->download(storage_path('app/'.$file));
   }

   
   public function upload()
   {
       return view('upload');
   }

   
   public function store(UploadFileRequest $request)
   {
 
       $filename = $request->fileName;    
       $file = $request->file('userFile');
       $extension = $file->getClientOriginalExtension(); 
       $saveAs = $filename . "." . $extension; 
       $file->storeAs('uploads', $saveAs, 'local'); 
       return response()->json(['success' => true]);
   }
}

因此,将上传的文件保存到磁盘是一种相当简单的方法。以下是上述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>

这是我们的/upload页面的样子:

这是一个非常典型的Blade文件示例,其中包含 HTML 表单和用于添加异步功能的 javascript/jQuery(因此页面不会刷新)。有一个基本的 <form> 标记,没有方法属性(我将在几秒钟内解释)和一个奇怪的动作属性,其值为 {{route(‘file.upload’)}}。在Blade中,这就是所谓的指令。指令只是函数的一个花哨名称——它们是Blade模板的特定函数,执行构建网页和 Web 应用程序常见的不同操作。为了更好地理解刀片可以做的所有很酷的事情,请查看此处的文档。在上面的例子中,我们使用 route 指令为我们的表单提交生成一个 URL。

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

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

在此之后,我们将我们的表单定义为正常,但是,请注意我们的表单参数的名称,userFile 和 fileName与我们的请求对象中定义的完全相同。如果我们忘记为请求对象中定义的给定参数包含输入(或拼写错误),请求将失败并返回错误,从而阻止原始表单请求访问位于 [email protected] 的控制器方法过程 。

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

更大的图景

让我们退后一步,看看我们在这个 Laravel 教程中做了什么。

此图描述了当前的应用程序(不包括高级细节):

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

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

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

另外,请注意上图中客户端和服务器之间的界线位置。这个概念对于你理解是绝对关键的,它将帮助你解决将来在处理各种问题时可能遇到的问题,例如,在任何给定时间可能发生的多个异步请求。分隔就在请求对象的边界。请求对象本身可以被认为是应用程序其余部分的“网关”……它对从 Web 浏览器传入的表单值进行初始验证和注册。如果它们被认为是有效的,那么它继续到控制器。之前的一切都在前端(“客户端”的字面意思是“在用户的计算机上”)。响应从应用返回到客户端,