Skip to content

Laravel基于项目实践的源码剖析 #2

Description

@suhao

Laravel源码剖析

源码剖析我们将从如下步骤逐步分析和实练,从而掌握基于Laravel的项目开发:

Activity

  1. pinned this issue on May 10, 2022
  2. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    目录结构与配置

    1. 目录结构
      Laravel的目录与文件很多,常用的有如下这些:
    • app: 当前应用相关的内容,如控制器, 中间件, 服务提供者等
    • config: 当前应用的所有配置文件
    • public: 外部可访问的Web目录,包括入口文件与前端静态资源
    • routes: 所有的路由定义文件
    • resources: 目录包含了视图和未编译的资源文件(如 LESS、SASS 或 JavaScript)
    • vendor: 包含安装的所有的 Composer 依赖包
    • .env: 环境配置信息,如应用, 数据库,会话, 邮箱,Redis等
    • artisan: Laravel 内置的命令行工具
    • composer.json,composer.lock: Composer依赖包的声明与锁定文件

    请注意需要将Public作为网站的入口

    1. 环境配置
    • 如果是使用Laragon|Composer安装的,.env文件会自动创建和配置好。否则需要手工更名.env.example为.env
    • .env中的内容可通过辅助函数env()调用:主要在config/目录下面的配置文件中进行调用, 如config/app.php
    1. 数据库

    在.env中可以设置数据库连接的相关参数:

    DB_CONNECTION=mysql
    DB_HOST=localhost
    DB_PORT=3306
    DB_DATABASE=blog
    DB_USERNAME=root
    DB_PASSWORD=root
    

    可以使用Adminer来连接数据库,并创建相关的数据表和导入数据等。

  3. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    MVC流程分析

    Laravel是基于MVC架构的开源PHP框架

    • M:Model,数据库操作,也包括队列缓存等
    • V:View,视图,由一个或多个模板组成,格式化输出响应数据
    • C:Controller,控制器,主要接收请求,选择模型,渲染视图等

    1. Web应用程序运行原理:URL请求 -> 路由解析 -> 执行程序

    Laravel中的所有Web访问,必须设置路由才会生效

    对php来说,一个页面,对应一个php脚本文件,以dev.tt/index.php为例:

    • Laravel将URL请求的地址,映射到index.php上的功能,成为路由技术
    • Laravel中路由文件是:routes/web.php
    • dev.tt/index.php:一个页面, 对应的就是一个应用程序
      • 程序就是可执行的代码, 代码为了复用通常使用函数进行封装,然后按名调用
      • 在Laravel中, 函数有二个载体: 闭包(匿名函数), 类中的方法
    • 浏览器地址栏输入: dev.tt/index.php, 就可以看到输出的HTML内容

    2. 控制器

    • 接受请求: 请求来自客户端, 客户端不限于浏览器, 可以是命令行, App(如小程序)等
    • 选择模型: 当涉及数据处理时, 需要调用对应的模型与不同的数据库进行交互
    • 渲染视图: 大多数操作都需要返回一个HTMl页面给用户,所以需要模板调度与渲染
    • Laravel中控制器默认应该创建在app/Http/Controllers目录下面
    • 控制器类的命名空间(App\Http\Controllers)与它的访问路径是一致的, 便于自动加载
    • 所有控制器必须继承控制器基类app\Http\Controllers.php,
    • 该基类与用户控制器类在同一目录,继承时不必使用带有命名空间名称的基类名
    • 创建控制器有二种方法: **手工创建,命令创建

    3. 模型

    • 从工程角度看, 模型是对现实世界的抽象, 通过研究模型可以推导出真实世界的情况
    • 计算机世界的模型,特指数据模型, 是对物理数据, 按照人类思维方式的抽象或简化
    • 传统的Web项目, 数据几乎全部来自数据库, 所以模型大多是与数据库相关的操作
    • 随着Web应用程序的日趋复杂,缓存,队列等不断的加入,使模型的定位也越来越模糊
    • 所以, Laravel中并未对模型设置默认或专用的目录, 一切交给用户决定
    • 做为新人, 应该从模型的最基本应用场景开始学习: **操作数据表
    • 虽然框架没有为模型设置默认位置, 但是模型总是与应用相关, 所以通常创建在app目录下面
    • 在app目录下, 可以直接创建一个模型类, 不过, 更推荐创建一个模型目录app/models统一管理
    • Laravel 的 Eloquent ORM 提供了一个漂亮, 简洁的 ActiveRecord 实现来和数据库交互
    • 每个数据库表都有一个对应的 [模型] 用来与该表交互
    • 可以通过模型查询数据表中的数据, 以及在数据表中插入新记录
    • 与控制器一样, 模型也有: 手工创建, 命令创建二种方式

    4. 视图

    • Laravel 使用的是 Blade模板引擎, 使用它创建的模板文件中, 可以直接使用原生的PHP代码
    • Blade模板引擎生成的模板文件:
      • 默认存放目录: resources/views
      • 默认扩展名是: .blade.php
    • 通常模板文件与控制器方法对应(但也并不总是如此)
    • Laravel没有给提供创建视图的命令, 但你可以通过安装composer扩展sven/artisan实现
  4. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    中间件

    • 中间件,并非Laravel首创, 也不是什么新概念
    • 中间件一直都存在, 只不过之前没有得到充分的重视
    • 中间件,顾名思义, 应该位于二个事物的中间,是哪二个事物呢?
    • 简单理解, 中间件位于用户请求与控制器之间, 其实还有基本的应用场景
    • 例如, 中间件可以过滤用户的请求, 或者拦截进入到控制器的请求数据
    • 所以, 中间件是请求的过滤器, 控制器的拦截器(保镖)

    1. 中间件执行流程

    中间件也是一个类文件, 主要有创建, 注册, 触发三个阶段

    • 创建阶段: 可以手工创建, 但更推荐使用artisan命令方式创建
    • 注册阶段: 注册工作在app/Http/kernel.php内核文件中完成
    • 触发阶段: 可以在路由中定义routes/web.php,或者在控制器的构造方法中触发

    2. 中间件创建

    • 中间件默认创建在: app/Http/Middleware目录下面, 当前已经内置了一些常用的中间件
    • 手工创建中间件, 推荐复制一个现成的, 然后将业务逻辑更新成自己的
    • 中间件不需要继承中间件的基类,也可以正常工作
    • 中间件中只有一个必须实现的方法handle()
    • 命令创建: php artisan make:middleware CheckNameMiddleware
    # app/Http/Middleware/CheckNameMiddleware.php
    # 功能: 检测当前URL访问的用户名,判断是否有权访问
    # 如果用户名name='admin', 则是管理员, 必须登录(跳转到登录页面),以获取更多的权限
    # 其它用户, 则直接进入页面,直接显示一行欢迎词即可
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    
    class CheckNameMiddleware
    {
        public function handle(Request $request, Closure $next)
        {
            // 如果请求中的name参数值不是admin, 则重定向到登录页面, 这里只是模拟
            if ($request->name === 'admin') {
                return redirect('login');
            }
            return $next($request);
        }
    }
    

    3. 中间件的注册

    中间件(以全局为例)的注册完全在app/Http/kernel.php中, 需要分二步完成:

    1. 将中间件注册/添加到protected $middleware=[...]数组中
    2. 为中间件创建路由别名/键名: protected $routeMiddleware = [...]数组
    protected $middleware = [
      ...
      // 注册自定义的中间件
      \App\Http\Middleware\CheckNameMiddleware::class,
    ];
    
    protected $routeMiddleware = [
      ...
      // 注册自定义中间件的路由
      'checkName' => \App\Http\Middleware\CheckNameMiddleware::class,
    ];
    

    4. 中间件的触发

    中间件涉及到请求, 控制器,视图等, 为了简化, 我们只在路由中进行测试: routes/web.php

    // 测试中间件, 为简化直接用闭包实现, 不做视图了
    // 这是中间件验证失败后的跳转路由
    Route::get('login', function () {
      return '<h1>登录页面</h1>';
    });
    
    // 中间件验证路由
    Route::get('home/{name}', function ($name) {
      return '<h3>欢迎用户'.$name. '回来</h3>';
    })->middleware('checkName');
  5. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    路由

    • 路由的最基本功能是将一个URL请求, 映射到一个程序程序(Web页面)
    • Laravel路由主要是通过Route这个Facade类来声明的
    • 路由主要是由路由动作和路由行为组成, 如: Route::get('hello', funciton (){...})
    • 路由参数主要用在闭包路由中
    • Laravel中的路由主要由二个文件定义:
      • web.php: 主要处理浏览器发起的URL请求, 这是我们学习的主要对象
      • api.php: 主要用于API接口请求,通常用在跨语言, 跨应用的环境中

    1. 路由动作

    Laravel中的路由地址, 遵循RESTful风格, 所以,具体的路由动作/类型,保持与RESTful一致

    动作/方法 SQL语句 实例 描述
    GET SELECT GET: http://dev.tt/staffs 获取资源
    POST CREATE POST: http://dev.tt/staffs 创建资源
    PUT UPDATE PUT: http://dev.tt/staffs/5 更新资源
    PATCH UPDATE PATCH: http://dev.tt/staffs/7 更新资源部分属性
    DELETE DELETE DELETE: http://dev.tt/staffs/10 删除资源

    常用的路由动作:

    Route::get('url', function () {...});
    Route::post('url', function () {...});
    Route::put('url', function () {...});
    Route::patch('url', function () {...});
    Route::delete('url', function () {...});

    如果一个请求,需要响应多个动作,可以这样做:

    // 可响应GET/POST请求
    Route::match(['get', 'post'], 'url', function () {...});
    
    // 可响应任何请求
    Route::any('url', function () {...});

    2. 路由参数

    • 路由是将URL请求映射到一个函数的技术,而函数通常都会有一个或多个参数
    • 参数通过URL传入到函数中, 是非常方便,且自然的想法
    • Laravel路由支持通过路由传参, 参数格式遵循RESTful规范
    • 参数根据是否必须赋值,可分为: 必选参数和可选参数二类

    2.1 必选参数

    • 所谓必选, 就是在路由中必须提供, 如果不提供,或类型错误都会报错
    • 必须使用一对花括号{...}包裹,多个参数之间使用/分隔,如add/{x}/{y}

    在routes/web.php中, 定义一个闭包路由进行演示

    Route::get('sum/{a}/{b}', function ($a, $b) {
      return "$a + $b = " . ($a + $b);
    });
    • 注意: 函数中的参数与URL中的参数, 只和顺序相关, 与名称无关, 显然名称一致可读性更好

    2.2 可选参数

    • 所谓可选, 就是在路由可以提供参数,如果不传参, 则使用默认值
    • 所以, 可选参数,在函数的参数列表中,必须提供默认值
    • 语法非常简单, 只需要在路由参数后面添加一个?号即可,如url/id?
    Route::get('sum/{a?}/{b?}', function ($x = 15, $y = 25) {
      return "$x + $y = " . ($x + $y);
    });
    
    // 浏览器: dev.tt/sum, 未传参则使用默认值, 返回 15 + 25 = 40
    // 浏览器: dev.tt/sum/30/35,传参则使用自定义的值 返回: 30 + 35 = 65

    2.3 参数类型约束(了解即可)

    • 可以对参数类型进行约束, 例如上例中的,二数相加, 二个操作数必须是数值才有意义
    • Laravel路由提供了where()方法,对参数类型进行约束, 可以使用正则做为条件
    • 放心, 只需要几个非常简单的正则即可,如\d+, \w+
    Route::get('sum/{a}/{b}', function ($a, $b) {
      return "$a + $b = " . ($a + $b);
    })->where('a', '\d+')->where('b', '\d+');
    • where()支持链接调用, 参数除字符串,还可以是数组,将多个条件写在数组中
    Route::get('sum/{a}/{b}', function ($a, $b) {
      return "$a + $b = " . ($a + $b);
    })->where(['a'=>'\d+', 'b'=>'\d+']);
    • 除了闭包路由外, 还可将参数传递给指定的控制器方法, 其实这样做更常用
    # 控制器方法: TestController.php
    public function sum($a, $b)
    {
      return "$a + $b = " . ($a + $b);
    }
    
    # 路由定义
    Route::get('sum/{a}/{b}', 'TestController@sum');

    3. 命名路由

    • 命名路由, 也叫别名路由, 就是使用一个语义化的字符串来代替(简化)一个较长的路由名称
    • 使用name()方法创建命名路由, 在需要访问路由的地方,使用route()进行解析并还原
    // 为当前路由'sum/{a}/{b}',创建一个命名路由: 'add'
    Route::get('sum/{a}/{b}', function ($a, $b) {
      return "$a + $b = " . ($a + $b);
    })->name('add');
    • 命名路由创建成功后, 就可以在视图,控制器或者其它路由中使用了:
    // 为了简化, 在另一个闭包路由中调用这个命名路由
    Route::get('res', function (){
        $url =route('add',[100,200]);
        return '<a href="'.$url.'">计算</a>';
    });
    • 还有一个辅助方法: url(), 功能与route()类似,但不支持命名路由, 修改一下res
    Route::get('res', function (){
        $url =url('sum',[100,200]);
        return '<a href="'.$url.'">计算</a>';
    });
    // 执行结果与上面是完全一样的

    4. 闭包路由

    • 其实前面我们已经在用闭包路由了, 大家应该不会感到陌生
    • 闭包, 其实就是一个匿名函数,如果应用比较简单, 不需要控制器和视图介入, 使用闭包最方便
    • 注意, 闭包中的返回数据, 不要使用echo,应该统一使用return , 由响应对象决定如何返回前端
    Route::get('dev', function () { return 'Hello dev.tt';});
    • 注意, 第二个参数必须是一个闭包, 不能是一个函数名称,例如这样做是错误的
    Route::get('dev', 'hello');
    function hello { return 'Hello dev.tt'; }

    5. 控制器路由

    • 这可能是大家用得最多的一种路由方式了, 其实所有的路由都可以用它代替的
    • 控制器默认创建在app\Http\Controllers目录下面
    • 控制器名称与方法之间用@符区隔开, 如DemoController@index
    namespace App\Http\Controllers;
    
    class DemoController extends Controller
    {
        public function index()
        {
          //...
        }
    }
    • 定义路由: Route::any('demo', 'DemoController@index');

  6. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    控制器

    1. 控制器与MVC

    1.1 控制器简介

    • 随着项目的复杂度提升, 不可能将所有业务逻辑全部写到路由的闭包中
    • 最常用也是最有效的方式,就是创建控制器, 将业务逻辑写到控制器的方法中
    • 前面学过, 控制器是MVC架构中的重要组成部分

    1.2 控制器与MVC

    • MVC: M模型(Model), V视图(View), C控制器(Controller)
    • 大多数项目是基于CURD操作来实现的, 那么他么的MVC流程是这样的:

    mvc

    • 从模型类获取数据,通过控制器,渲染到视图页面中

    • 从视图页面中获取用户提交的请求/数据:

      • 涉及数据库,则交给模型类处理,并将结果返回或存储到数据库中
      • 不涉及模型类, 则直接在控制器中处理,并返回给用户
    • 控制器不应该承载太多的功能,他更适合充当路由请求的对象

    • 控制器并非应用访问的唯一入口,例如Artisan, 队列,任务调度等

    • 过度依赖控制器会带来以后应用扩展的麻烦,所以,控制器的功能应该专业,单一

    1.3 控制器的功能

    • 获取HTTP请求, 并进行简单的验证
    • 然后将这些请求交给真正能处理这些请求的职能模块完成
    • 类似于公司的前台或企业的门卫, 识别请求, 验证身份, 通知职能部门处理

    2. 控制器的创建

    2.1 控制器命名空间与基类

    • 命名空间: app/Http/Controllers

    • 继承基类: app/Http/Contrllers/Contrller.php

    • 创建命令: php artisan make:controller StaffController

    • 准备工作,将之前创建的控制器, 模型和无关视图删除, 全部重新创建

    // 默认控制器代码
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    
    class StaffController extends Controller
    {
        //
    }
    • 创建一个默认方法, 获取所有员工信息
    namespace App\Http\Controllers;
    
    use App\Models\StaffModel;
    use Illuminate\Http\Request;
    
    class StaffController extends Controller
    {
        //获取员工列表
        public function index()
        {
            // view(): 选择视图模板文件,注意这里使用目录结构语法"."
            // StaffModel::all(): 模型中的all()方法返回全部表中全部记录
            return view('staffs.index')
            ->with('staffs', StaffModel::all());
        }
    }
    • 模型使用之前创建的, 代码如下:
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class StaffsModel extends Model
    {
        // 模型名单数, 默认对应复数名的数据表
        protected $table = 'staffs';
    
        // 默认对应的主键字段名称是id
        protected $primaryKey = 'id';
    
        // 因为create_at,update_at使用了laravel默认名称, 所以可以直接这样写
        // 这样可以保证,以时间戳的形式存入时间
        protected $dateFormat = 'U';
    }
    • 视图: resource/views/staffs, 视图支持.php和.blade.php二种语法
    • 以下是原生PHP语法
    // 原生PHP语法
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>员工管理</title>
    </head>
    <body>
    <h3>员工列表</h3>
    <ul>
    <?php foreach ($staffs as $staff): ?>
        <li><?=$staff['id']?>: <?=$staff['name']?> email: <?=$staff['email']?></li>
    <?php endforeach;?>
    </ul>
    </body>
    </html>
    • 以下是Blade模板语法
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>员工管理</title>
    </head>
    <body>
    <h3>员工列表</h3>
    <ul>
        @foreach ($staffs as $staff)
        {{-- 为了以示区别, 将email邮箱字段修改为salary工资字段 --}}
        <li>{{$staff['id']}} : {{$staff['name']}} salary: {{$staff['salary']}}</li>
        @endforeach
    </ul>
    </body>
    </html>
    • 存在以上二种格式的模板时, .blade.php的模板文件优先读取

    • 创建路由, 访问该方法

    Route::get('staffs', 'StaffController@index');

    2.2 获取用户的输入

    以添加一个员工数据为例

    • 添加操作需要二步,首先渲染一个表单, 然后再将提交的数据写到表中
    • 先添加二条路由: routs/web.php
    // create(): 渲染表单
    // Route::get('staffs/create', 'staffsController@create');
    
    // 如果只是渲染一个模板, 没有必要创建一个控制器方法的,可以直接用闭包简化
    //Route::get('staffs/create', function () {
    //    return view('staffs.create');
    //});
    
    // 还可以使用路由中的view()方法进一步简化
    Route::view('staffs/create', 'staffs.create');
    
    
    // store(): 存储数据
    Route::post('staffs/store', 'StaffController@store');
    • 模板文件: resources/views/staffs/create
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>添加员工</title>
    </head>
    <body>
    <h3>添加员工</h3>
    {{-- url('路由'): 将路由解析为对应的真实请求地址 --}}
    <form action="{{url('staffs/store')}}" method="post">
        {{-- 表单令牌,会自动转为隐藏域发送,没有它请求会被服务器拒绝 --}}
        @csrf
        <p>
            <label for="name">姓名:</label>
            <input type="text" name="name" id="name">
        </p>
    
        <p>
            <label for="male">性别:</label>
            <input type="radio" name="sex" id="male" value="1"><label for="male">男</label>
            <input type="radio" name="sex" id="female" value="0"><label for="female">女</label>
        </p>
    
        <p>
            <label for="age">年龄:</label>
            <input type="number" name="age" id="age" min="18" max="60">
        </p>
    
        <p>
            <label for="salary">工资:</label>
            <input type="number" name="salary" id="salary" min="2500">
        </p>
    
        <p>
            <label for="email">邮箱:</label>
            <input type="email" name="email" id="email">
        </p>
    
        <p>
            <label for="mobile">手机:</label>
            <input type="tel" name="mobile" id="mobile">
        </p>
    
        <p>
            <label for="password">密码:</label>
            <input type="password" name="password" id="password" value="123456">
        </p>
    
        <p>
            <button>保存</button>
        </p>
    </form>
    </body>
    </html>
    • StaffController: 获取用户请求的数据,并处理
    namespace App\Http\Controllers;
    
    use App\Models\StaffModel;
    use Illuminate\Http\Request;
    
    class StaffController extends Controller
    {
        //获取员工列表
        public function index()
        {
            return view('staffs.index')->with('staffs', StaffModel::all());
        }
    
        // 渲染添加表单
        public function create()
        {
            return view('staffs.create');
        }
    
        // 添加数据操作
        // 依赖注入: 请求对象, 模型对象
        public function store(Request $request, StaffModel $staff)
        {
            // 获取表单提交的数据,这里直接使用魔术属性
            $staff->name = $request->name;
            $staff->sex =  $request->sex;
            $staff->age = $request->age;
            $staff->salary =  $request->salary;
            $staff->email = $request->email;
            $staff->mobile =  $request->mobile;
            $staff->password = sha1($request->password);
            // 将获取到的全部表单数据存储到模型对应的数据表中
            $staff->save();
            // 保存成功后, 跳转到所有员工列表页
            return redirect('staff');
        }
    }

    2.3 再谈依赖注入

    2.3.1 门面与依赖注入之间的关系

    • 门面(Facade)是Laravel为大部分类提供的简单调用接口, 可轻松获取各种请求数据
    • 例如用户的输入, Session, Cookie等,但并不是所有类都是门面接口
    • 其实对于这些类, 我们还可以可通过更底层的依赖注入来调用它们
    • 本质上说过, 门面仅仅是一种设计模式,是对底层API做的上层静态代理,主要是为了简化调用
    • 所以, 凡是可以用门面调用的方法,理论上都是可以通过依赖注入来实现的
    • 反过来则不一定成立, 依赖注入的类方法,却不一定可以通过门面调用,除非你自定义这个门面

    2.3.2 依赖注入与服务容器之间的关系

    • 服务容器: 是一个绑定了多个接口, 以及它的具体实现类的容器

    • 依赖注入: 代码以接口(类型提示)做为参数,不必传入具体实现,在代码运行时,会根据配置自动从服务容器获取到接口对应的实现类,执行具体的接口方法,从而提高代码的可维护性

    • Laravel中, 所有的控制器方法(包括构造器)全部都在服务容器中解析

    • 这意味着, 所以通过控制器方法传入的,可以被容器解析的接口(类型提示类)对应的服务实现类都可以被自动实例化并注入到当前控制器方法中,我们把这个自动化的过程,称之为"依赖注入"

    • 最常用的当然就是Request $request,这是典型的依赖注入模式

    • 推荐开发时, 尽可能使用依赖注入来获取用户输入

    • 例如,通过$request对象获取session,cookie等


    3. 资源控制器

    • 在实际开发工作中, 对于控制器方法和参数的命名,很麻烦,很容易犯"命名恐惧症"
    • Laravel非常人性的为开发者量身定制了一套完整的,用来定义控制器方法的一套约束规则
    • 该套方法使用REST/CURD为参考,遵循RESTful, 按CURD流程来制定
    • 使用以上规则创建的控制器, 就是大名鼎鼎的资源控制器
    • 当你的需求比较规范和简单时,完全可以使用它来简化你的开发项目

    3.1 创建资源控制器

    非常简单, 只需要在创建控制器时, 添加上--resource参数选项即可

    • 创建命令: php artisan make:controller ArticleController --resource

    • 在app/Http/Controllers/会自动生成控制器ArticleController.php,内容如下:

    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use Illuminate\Http\Response;
    
    class ArticleController extends Controller
    {
        /**
         * Display a listing of the resource.
         *
         * @return Response
         */
        public function index()
        {
            //
        }
    
        /**
         * Show the form for creating a new resource.
         *
         * @return Response
         */
        public function create()
        {
            //
        }
    
        /**
         * Store a newly created resource in storage.
         *
         * @param Request $request
         * @return Response
         */
        public function store(Request $request)
        {
            //
        }
    
        /**
         * Display the specified resource.
         *
         * @param  int  $id
         * @return Response
         */
        public function show($id)
        {
            //
        }
    
        /**
         * Show the form for editing the specified resource.
         *
         * @param  int  $id
         * @return Response
         */
        public function edit($id)
        {
            //
        }
    
        /**
         * Update the specified resource in storage.
         *
         * @param Request $request
         * @param  int  $id
         * @return Response
         */
        public function update(Request $request, $id)
        {
            //
        }
    
        /**
         * Remove the specified resource from storage.
         *
         * @param  int  $id
         * @return Response
         */
        public function destroy($id)
        {
            //
        }
    }

    3.2 创建资源控制器的路由

    • 为简化资源控制器的路由设置, laraval为我们提供一个路由方法用来快速创建所有资源路由
    • 在web.php中添加:
    // 一次性注册全部的UserControler资源控制器中的全部路由
    Route::resource('article', 'ArticleController');
    • 终端执行命令: php artisan route:list, 可以查询当前的所有路由信息列表
    • 控制器中的每一个方法,对应的请求类型,路由都要遵循RESTful规则,约定如下:
    请求类型 路由地址 命名路由 控制器方法 描述
    GET article article.index ArticleController@index 展示所有文章
    POST article article.store ArticleController@store 获取表单提交数据并保存文章
    GET article/create article.create ArticleController@create 发布/添加文章表单页面
    GET article/{article} article.show ArticleController@show 展示单个文章
    PUT/PATCH article/{article} article.update ArticleController@update 获取编辑表单输入并更新文章
    DELETE article/{article} article.destroy ArticleController@destroy 删除单个文章
    GET article/{article}/edit article.edit ArticleController@edit 编辑文章表单页面
    • 与下面的路由定义等价的
    // 展示所有文章
    Route::get('article', 'ArticleController@index')->name('article.index');
    // 获取表单提交数据并保存文章
    Route::post('article', 'ArticleController@store')->name('article.store');
    // 添加文章表单页面
    Route::get('article/create', 'ArticleController@create')->name('article.create');
    // 展示单个文章
    Route::get('article/{article}', 'ArticleController@show')->name('article.show');
    // 获取编辑表单输入并更新文章
    Route::put('article/{article}', 'ArticleController@update')->name('article.update');
    // 删除单个文章
    Route::delete('article/{article}', 'ArticleController@destroy')->name('article.destroy');
    // 编辑文章表单页面
    Route::get('article/{article}/edit', 'ArticleController@edit')->name('article.edit');
    • 路由举例: 渲染文章编辑页面为例
    • 路由定义: Route::get('article/{article}/edit', 'ArticleController@edit')->name('article.edit');
    • 修改ArticleController@edit方法:
    public function edit($id)
    {
        return '编程文章: ' . route('article.edit', [$id]);
    }
    // route('命名路由', [参数列表])
    // url('路由地址')
  7. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    视图与参数

    1. 视图简介

    • 在日常开发过程中, 除了API接口会返回指定格式的字符串外(如JSON/XML), 大多路由返回的都是HTML文档
    • 视图是MVC模式中的View部分, 大部分视图都是以HTML文档格式返回, PHP脚本也是生成HTML文档返回的
    • Laravel支持三种格式的视图文档:
      • .php: 原生的PHP脚本
      • .blade.php: 通过Blade模板引擎解析的PHP文档, 建议使用它,以简化模板设计
      • .css: CSS样式表文档(了解即可)
    • .php, .blade.php之前学习过, 现在介绍一下css
    • 先创建css文档: resources/staffs/css/style.css
    body {
        background-color: lightblue;
    }
    • 添加路由来解析css文档: route/web.php
      视图渲染的函数,有view()和View::make()二个,第一个之前用过
    // 解释css文件
    Route::get('staffs/style', function () {
    //    return view('staffs.style');
        return View::make('staffs.style');
    });
    
    // 路由仅是渲染一个视图,可以用视图路由进一步简化
    Route::view('staffs/style', 'staffs.style');

    2. 视图传参

    • 视图传参, 可以在view参数中实现, 也可以用视图方法with()链式传参
    • 实际上之前我们已经用过视图传参了
    • 在staffsController控制器中创建一个show($id)用来显示指定ID的记录
    use Illuminate\Support\Facades\DB;
    public function show($id)
    {
        // 获取记录
        $staffs = DB::table('staffs')->where('id',$id)->first();
        // 测试返回结果
        dd($staffs);
    
        // 参数做为view()第二个参数,  以关联数组键值对的形式传入到模板中
        return view('staffs.show', ['staffs'=>$staffs]);
        // 使用with()方法给模板赋值, 多个变量可使用关联数组
        return view('staffs.show')->with(['staffs'=>$staffs]);
        // 如果只有一个变量,可以只传入变量名和值即可
        return view('staffs.show')->with('staffs', $staffs);
    }
    • 创建路由: route/web.php
    Route::get('staffs/show/{id}', 'staffsController@show');
    • 视图: resources/staffs/show.blade.php
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>显示一条记录</title>
    </head>
    <body>
    {{--返回满足条件和首条记录--}}
    
    {{--使用原生PHP语法输出变量--}}
    {!! print_r($staffs) !!}
    
    <ul>
        <li>ID: {{$staffs->id}}</li>
        <li>姓名: {{$staffs->name}}</li>
        <li>邮箱: {{$staffs->email}}</li>
        <li>工资: {{$staffs->salary}}</li>
        <li>手机: {{$staffs->mobile}}</li>
    </ul>
    </body>
    </html>

    引入子视图

    @include('admins.public.init');

  8. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    Blade模板引擎

    1. Blade模板引擎简介

    • php本身就是最优秀的模板语言

    • 使用原生php编写的模板,高效但不简洁

    • 大量的php与html代码混编,造成代码耦合度过高,易出错,难阅读与维护

    • 模板引擎,即可以实现代码的简洁, 又兼顾了PHP语言的高效,非常受欢迎

    • 流行的PHP模板引擎很多,如Smarty, Twig, Plates等

    • Laravel中使用的是Blade, 当然也你可以更换成你熟悉的模板引擎,但不推荐

    • 任何一款模板引擎,都必须三个基本的功能:

      • 变量显示: 包括自定义变量与系统变量
      • 流程控制: 循环, 判断等
      • 模板继承: 实现多个PHP文件的功能组合
    • 如果模板中使用了Blade模板引擎语法,必须将模板文件扩展名为: .blade.php

    • 模板文件默认位置: resources/views

    • 编译后的模板文件: storage/framework/views/

    • 所谓编译: 实际就是使用原生PHP语法替换掉模板文件中的Blade指令后生成的php文件


    2. 变量显示

    • {{ $variable }}: 显示变量(包括自定义和全局系统变量)
      • 显示变量,并会自动进行HTML字符的转义, 避免XSS(称跨站脚本攻击)攻击
      • 对应PHP代码:<?php echo htmlentities($variable); ?>
    • {!! $variable !!}: 显示原始变量值
      • 不会调用htmlentities()函数过滤变量
      • 对应的PHP代码: <?php echo $variable; ?>
      • 适用于富文件数据的渲染(如发布文单的编辑器提交的数据)
    • @{{ $variable }}: 原样显示, 不解析变量
      • 众所周知, Laravel配合的最佳前端框架是Vue.js,而Vue.js使用{{var}}显示变量
      • 为了使Vue正确解析HTML模板,需要在它的变量语法前加上@,使它保持前端原有含义
      • 此时, Blade引擎会跳过/忽略, 带有@前缀的{{var}},并将@删除,便于Vue正常解析它
    • 注释: {{-- 注释内容 --}}, 注释只会显示在Blade模板中, 不会出现最终生成的HTML文档中
    • @php ... @endphp: 执行原生的php代码, 有时可能会不可避免的需要写原生代码,但并不推荐

    3. 流程控制

    3.1 条件判断

    3.1.1 @if ... @else ... @elseif ... @endif

    • 这是一组双标签,必须要有结束标记

    • 下面经典的成绩判断为例,进行条件判断流程控制的演示:

    • 创建一个视图路由: route/web.php

    // 演示模板中的流程控制
    // 使用Route::view(), 第三个参数是给模板的赋值
    Route::view('staffsss/demo1', 'staffsss.demo1', ['grade'=> 77]);
    • 创建对应的视图/模板文档: resources/views/staffsss/demo1.blade.php
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>流程控制</title>
    </head>
    <body>
    
    @if ($grade >= 60 && $grade < 80)
        <h3 style="color: blue">{{ $grade }}分, 合格</h3>
    @elseif ($grade >= 80 && $grade <= 100)
        <h3 style="color: green">{{ $grade }}分,优秀</h3>
    @else
        <h3 style="color: red">{{ $grade }}分,等着补考吧</h3>
    @endif
    
    </body>
    </html>
    • 编译后生成的原生语句是:
    <?php if($grade >= 60 && $grade < 80): ?>
        <h3 style="color: blue"><?php echo e($grade); ?>分, 合格</h3>
    <?php elseif($grade >= 80 && $grade <= 100): ?>
        <h3 style="color: green"><?php echo e($grade); ?>分,优秀</h3>
    <?php else: ?>
        <h3 style="color: red"><?php echo e($grade); ?>分,等着补考吧</h3>
    <?php endif; ?>
    • e(), 实际上就是htmlentities(),在Laravel中的别名

    • 还有二组条件指令的扩展,类似于语法糖, 用于简化if语句,你完全可以用if代替

      • @unless() ... @endunless, 等价@if (!condition)条件取反
      • @isset() ... endisset, 等价于@if (isset())
      • @empty() ... endempty, 等价于@if (empty())

    3.1.2 @switch () ... @endswitch

    • 与原生的switch ()结构对应, Blade还提供了@case, @break, @default配合@switch ()
    • 将上面的案例用@switch ()改写, 执行结果与上面案例是完全一致
    @switch (true)
        @case ($grade >= 60 && $grade < 80)
        <h3 style="color: blue">{{ $grade }}分, 合格</h3>
        @break
        @case ($grade >= 80 && $grade <= 100)
        <h3 style="color: green">{{ $grade }}分,优秀</h3>
        @break
        @default
        <h3 style="color: red">{{ $grade }}分,等着补考吧</h3>
    @endswitch
    • 编译后的原生php语句是:
    <?php switch(true):
        case ($grade >= 60 && $grade < 80): ?>
        <h3 style="color: blue"><?php echo e($grade); ?>分, 合格</h3>
        <?php break; ?>
        <?php case ($grade >= 80 && $grade <= 100): ?>
        <h3 style="color: green"><?php echo e($grade); ?>分,优秀</h3>
        <?php break; ?>
        <?php default: ?>
        <h3 style="color: red"><?php echo e($grade); ?>分,等着补考吧</h3>
    <?php endswitch; ?>

    3.2 循环结构

    • 与原生循环对应, Laravel中也有三种:@for(),@foreach(),@while()
    • 他们全是双标签,基本语法如下:
    // for 循环
    @for ($i = 0; $i < count($staffssss); $i++)
        {{ $talk->name }} ({{ $talk->email }} <br>
    @endfor
    
    // foreach 循环
    @foreach ($staffssss as $staffsss)
        {{ $talk->name }} ({{ $talk->email }} <br>
    @endforeach
    
    // while 循环  
    @while ($item = array_pop($items)) 
        {{ $talk->name }} ({{ $talk->email }} <br>
    @endwhile
    • 案例, 将staffsss表中的员工记录全部循环遍历出来
    • 路由如下: route/web.php
    // 通过模型取出staffsss表中所有数据
    $staffssss = \App\Models\staffsssModel::all();
    //dump($staffssss);
    
    // 将集合类型转为普通的数组,便于在模板中调用
    $staffssss = \App\Models\staffsssModel::all()->toArray();
    //dump($staffssss);
    
    //创建路由, 模板赋值
    Route::view('staffsss/demo2', 'staffsss.demo2', ['staffssss'=>$staffssss]);
    • 创建对应的视图/模板文档: resources/views/staffsss/demo2.blade.php
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>流程控制</title>
    </head>
    <body>
    
    {{--@for--}}
    @for ($i = 0; $i < count($staffssss); $i++)
        <p>姓名: {{$staffssss[$i]['name']}}, 邮箱: {{$staffssss[$i]['email']}}</p>
    {{--    只输出前三条记录--}}
            @break ($i === 2)
    @endfor
    
    <hr>
    
    {{--@foreach--}}
    @foreach ($staffssss as $key => $staffsss)
        {{-- @continue   跳过一些记录--}}
        @continue ($key > 2 && $key < 10)
        <p>姓名: {{$staffsss['name']}}, 工资: {{$staffsss['salary']}}, key: {{$key}}</p>
    @endforeach
    
    <hr>
    
    {{--@while--}}
    {{--设置循环条件初始值--}}
    @php $counter = 0; @endphp
    @while ($counter < count($staffssss))
        <p>姓名: {{$staffssss[$counter]['name']}}, 工资: {{$staffssss[$counter]['salary']}}</p>
    {{--    更新循环条件,非常重要--}}
        @php $counter++; @endphp
    @endwhile
    </body>
    </html>

    4. 模板继承

    • 模板继承涉及到布局文件(父视图) 和 布局实现文件(子视图)
    • 父视图中主要是定义页面的基本结构和需要在子视图实现的区块
    • 子视图中主要是实现父视图中定义的区块(也可引用父视图中的内容)
    • 模板继承的原理,与PHP中的父类与子类之间的关系非常像,可以联想学习

    4.1 布局文件(父视图)

    主要涉及二个Blade指令: @yield() 和 @section() / @show,语法如下:

    4.1.1 @yield('区块名', '默认内容')

    • 用于指定需要子视图继承实现的内容区块
    • 第二个参数给该指令用于指定子视图未继承时的默认值

    4.1.2 @section('区块名') 默认内容 @show

    • 功能与@yield一样,用于指定需要子视图继承实现的内容区块
    • 默认内容写在指令中间,并且可以被子视图中的@parent指令调用

    4.1.3 @yield和@section/@show区别

    • @yield指定的默认内容对子视图不可见
    • @section/@show指定的默认内容子视图可以通过 @parent 访问

    4.1.4 父视图案例

    父视图(布局文件位置): resources/views/layouts/master.blade.php

    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
    {{--    <title>{{ $title ?? '布局文件'}}</title>--}}
    {{--    用区块来定义标题,并不是好主意, @yield('名称', '默认值')--}}
        <title>@yield ('title', '布局模板')</title>
    
        <style>
        @section ('css')
            .container {
                display: flex;
                flex-direction: column;
                min-height: 800px;
            }
            .header, .footer {
                flex: 0 0 60px;
                background-color: lightblue;
            }
            .main {
                flex-grow: 1;
                margin: 5px 0;
                background-color: wheat;
            }
             @show
        </style>
    
    </head>
    <body>
    {{--使用 @yield 和 @section/@show 指令在布局文件中定义区块/插槽--}}
    <div class="container">
        <div class="header">
    {{--        @yield定义的区块: 只能覆盖, 不可扩展--}}
    {{--        @yield 定义的区块的默认内容,是写到第二个参数中的, 子视图无法调用--}}
            @yield ('header', '头部')
        </div>
    
        <div class="main">
    {{--        @section/@show定义的区块,可覆盖, 可扩展--}}
            @section ('main')
    {{--            默认内容--}}
                <h1>父模板中区块的的默认内容</h1>
            @show
        </div>
    
        <div class="footer">
            @yield ('footer', '底部')
        </div>
    </div>
    
    </body>
    </html>

    4.2 继承布局文件(子视图)

    主要涉及二个Blade指令: @extends() 和 @section() / @endsection,语法如下:

    • extends('父视图名称'): 指定需要继承的布局文件
    • @section('区块')/@endsection: 实现布局文件中@yield/@section定义的区块
    • 子视图: resources/views/layouts/master.blade.php
    {{--子模板中只允许出现@extends, @section/@endsection 指令--}}
    
    {{虽然子视图与父视图都在layouts目录下,但仍需要指定父视图所属目录,因为系统以绝对路径方式定位视图}}}
    @extends('layouts.master')
    
    
    {{--对于title,这种非常简单的,直接传入第二个参数就可以了,类似变量赋值,没必要使用双标签--}}
    {{--将标题中的@yield定义的区块,替换--}}
    {{--@section: 传入第二个参数后, 就不再需要结束标签@endsection--}}
    {{--@section('title', '公司网站后台')--}}
    
    @section('header')
    {{--    @parent在@yield定义的区块中是无效的--}}
        @parent
       <h2>导航部分</h2>
    @endsection
    
    @section('main')
    {{--    @parent: 引用父级布局中的内容--}}
        @parent
    <ul>
        <li>列表1</li>
        <li>列表2</li>
        <li>列表3</li>
        <li>列表4</li>
    </ul>
    @endsection
    
    @section('footer')
        <h2>页面底部</h2>
    @endsection
    • @extends指定的布局文件,同样必须位于resources/views目录下面,目录之间用.区隔

    • @section会依次实现布局中定义的区块,它与父视图中的@section指令,通过第一个参数,即'区块名称'建立关联

    • @section有二种语法格式:

      • 简化: @section('区块', '内容'), 不需要结束标签@endsection,内容放在第二个参数中即可
      • 完整: @section('区块') 内容 @endsection,内容直接放在标签之间即可
    • 对于子视图中没有实现的父视图区块,则直接显示原父视图中@yield/@section中默认内容

    • 对于子视图中已经实现的父视图区块,再根据区块定义方式,确定是否允许引用父视图图中的默认内容

    • 对于父视图中@yield创建的变量,如title,可使用简化语法: @section('block','value')传第二个参数实现

    • �对于父视图中的css区块@section('css')/@show, 子视图如果没有实现, 那么就直接引用/继承了原父视图中的内容

    • 当然,我们也可以在子视图中来扩展父视图的css样式, 在子视图中添加代码如下:

    @section ('css')
        @parent
        .container {
        color: red;
        }
    @endsection
    • 此时,你会看到子视图页面中的文本全部更新成了红色
    • 最后再强调一下, 页面中的区块显示顺序由布局(父视图)确定, 与子视图中的实现顺序没有关系
    • 也就是说, @section('footer') <h2>页面底部</h2> @endsection最到子视图最前面也无所谓

    4.3 导入组件

    • 导入组件涉及二条指令: @include(), @each()
    • @include(): 导入单个组件, 突破继承限制,可横向扩展功能,与Trait类似
    • @each(): 循环导入单个组件,用在需要循环遍历某个集合的场景下

    4.3.1 导入单个组件/模块

    • 路由:
    //include导入组件测试
    Route::view('include', 'include');
    • 视图:
    • 组件也是一个模板: resources/views/button.blade.php
      // 组件: 以添加一个课程按钮为例
      <button>{{ $course }}</button>
    • 创建视图resources/views/include.blade.php
    • 语法: @include('组件视图', [组件中需要用到的变量赋值])
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>导入组件</title>
    </head>
    <body>
    <h3>你喜欢的课程有哪些?</h3>
    @include('button', ['course'=>'PHP'])
    </body>
    </html>
    • 游览器访问: dev.tt/include

    4.3.2 循环导入多个组件/模块

    • 路由:
    //each循环导入组件测试
    Route::view('each', 'each', ['courses'=>['php','javascript','python']]);
    • 视图:

    • 复用上面的课程组件:<button>{{ $course }}</button>

    • 语法: @each('组件视图', $遍历的集合变量, '循环变量', '集合为空时的视图')

    • 根据语法,我们应该准备二个视图/模板

    • 创建集合为空的视图:resources/views/empty.blade.php

    <h3 style="color: red">空空如也~~</h3>
    • 创建循环组件的视图resources/views/each.blade.php
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>each导入组件</title>
    </head>
    <body>
    <h3>你喜欢的课程有哪些?</h3>
    @each('button', $courses, 'course', 'empty')
    
    {{--将集合变量置空再测试--}}
    @php $courses = []; @endphp
    @each('button', $courses, 'course', 'empty')
    </body>
    </html>
    • 游览器访问: dev.tt/each

    4.3 插槽与组件

    • Laravel 借鉴了 Vue 中的 slot 与 component 思想,使用插槽分发内容

    • 涉及一个变量:$slot 和 一条指令@component

    • $slot变量必须用在组件中, @component必须用在调用该组件的视图中

    • 组件与插槽使得代码片断与布局,更加的语义化,也更符合思维习惯和人性化

    • 下面以一下简单的JS弹窗进行演示

    • 制作弹窗组件: resources/views/alert.blade.php

    <script>
        alert('{{ $slot }}')
    </script>
    • 创建路由:
    // component 测试
    Route::view('component', 'component');
    • 创建路由中指定的插槽视图:resources/views/component.blade.php
    • 语法: @component('组件名称') 组件中$slot变量的值 @endcomponent
    @component('alert')
        验证通过
    @endcomponent
    • 打开游览器: dev.tt/component,会看到一个显示'验证通过'的js弹窗

    理解并掌握以上Blade模板引擎的基础知识, 相信您已经可以写出一个规范的视图模板文档了

  9. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    数据库操作

    1. 原生查询

    Laravel 默认全部采用PDO实现数据库的CURD操作
    参数占位符,支持匿名(?)与命名二种语法(:name)
    约定,全部采用闭包路由演示(web.php)

    1.1 原生读/查询操作

    • select($sql, [参数列表]): 获取满足条件的记录, 返回stdClass对象数组
    // 获取指定ID的记录
    Route::get('db/select/{n?}', function ($n=1){
        $res = DB::select('SELECT `id`,`name`,`email` FROM `staffs` LIMIT 0, :n', ['n'=>$n]);
        // 返回只有一个对象元素的数组
        var_dump($res);
        // 格式化打印
        echo '<pre>'. var_export($res,true);
    
        // 因为这种格式非常的友好, Laravel给我们封装一个方法dd()/dump()更加优雅的实现了这个功能
        dump($res);
        dd($res);   // dd() = dump() + die, 输出结果后立即终止代码执行
    });
    • 这种结果集的显示效果是如何实现的呢, 我们来模拟一下
    • 分析, 每一个对象元素,使用JSON格式的对象字面量表示,而PHP中并没有对象字面量的语法
    • 而PHP中并没有像JS,Python一样的对象字面量的语法,所以需转换,使用函数json_encode()
    • json_encode(): 将对象/数组转为JSON格式字符串
    // 将对象数组中的每一个元素(对象)转为JSON字符串
    
    // 1. 获取到结果数组中每一个元素的值,并组成一个新数组返回
    $values = array_values($res);
    
    // 2. 遍历处理每一个值,转换为JSON字符串
    $jsonArr = array_map(function($value){
        return json_encode($value);
    }, $values);
    
    // 3. 现在看上去就和dump/dd()显示基本一致(除了样式不同)
    print_r($jsonArr);

    1.2 原生更新操作

    • DB::update($sql, [参数列表]): 更新记录, 返回更新行数
    // 更新指定ID的记录: 仅更新年龄age和工资salary字段
    Route::get('db/update/{id}', function ($id) {
        // 被更新字段的值, 应该来自表单中POST变量, 这里用字面量进行模拟
        $_POST['age'] = 30;
        $_POST['salary'] = 8488;
        list($age, $salary) = [$_POST['age'], $_POST['salary']];
    
        // 参数使用匿名占位符: ? 问号
        $num = DB::update('UPDATE `staffs` SET `age`= ?, `salary`= ? WHERE `id` = ? ', [$age, $salary, $id]);
    //    $res = DB::update('UPDATE `staffs` SET `age`= 20 WHERE `id` =  :id', ['id'=>$id]);
    
        return '成功的更新了: ' . $num . ' 条记录';
    
    })->where('id', '\d+');

    1.3 原生新增操作

    • DB::insert($sql, $data): 新增记录, 成功返回true
    Route::get('db/insert', function () {
        // 新增的数据,实际开发中应该来自用户提交的表单
        $data = [
            'name'=> '东方不败',
            'sex'=>1,
            'age'=>30,
            'salary'=>8000,
            'email'=>'dfbb@php.cn',
            'mobile'=> '15800448811',
            'password'=>sha1('123456'),
            'created_at'=>time(),
            'updated_at'=>time(),
        ];
        // 字段列表
        $fields = ' (`name`,`sex`,`age`,`salary`,`email`,`mobile`,`password`,`created_at`, `updated_at`) ';
        // 值列表: 采用命名占位符
        $values = ' (:name, :sex, :age, :salary, :email, :mobile, :password, :created_at, :updated_at) ';
        // SQL语句
        $sql = 'INSERT `staffs` ' . $fields . ' VALUES ' . $values;
        // 执行新增操作, 成功则返回受影响的记录数,即新增数量
        $num = DB::insert($sql, $data);
    
        return '成功的新增了: ' . $num . ' 条记录';
    });

    1.4 原生删除操作

    • DB::delete($sql, $where): 删除记录, 返回受影响的行数
    Route::get('db/delete/{id?}', function ($id = null) {
        if (is_null($id)) return  '条件不能为空';
        else $num = DB::delete('DELETE FROM `staffs` WHERE `id` = ?', [$id]);
    
        return '成功的删除了: '. $num . ' 条记录';
    })->where('id', '\d+');

    2. 查询构造器

    1. 查询构造器, 是用户与数据库交互的重要工具,是数据库功能的底层核心组件
    2. 采用面向对象方式,基于链式方法调用完成, 模型也直接或间接依赖于它
    3. 基于DB门面类,调用table()构造一个基于指定数据表的构造构建器

    2.1 查询记录

    • get(columns|*): 获取满足条件的所有记录,参数是字段列表(可省),返回Collection
    • first(columns|*): 获取满足条件的首条记录,参数是字段列表(可省), 返回单个stdClass
    • value(column): 获取某一列的当前值,返回单值
    • pluck(value, key): 获取指定列值与对应主键组成的关联数组(很实用的功能)
    • chunk(n,callback): 分批/块处理超大结果集,防止内存爆仓, 返回布尔值
    Route::get('db/get', function () {
      // get(): 获取满足条件的所有记录,可指定字段
      $res = DB::table('staffs')->get('*');
      $res = DB::table('staffs')->get(['id', 'name', 'email']);
    
      // first(): 获取满足条件的首条记录,返回单个对象stdClass
      $res = DB::table('staffs')->first();
      $res = DB::table('staffs')->first(['id', 'name', 'email']);
    
      // value(column): 获取某一列的当前值,返回单值
      $res = DB::table('staffs')->value('name');
    
      // pluck(v,k): 获取指定列值与对应主键组成的关联数组, 返回数组
      $res = DB::table('staffs')->pluck('name','id');
    
      // chunk(n,callback): 分批/块处理超大结果集,防止内存爆仓, 返回布尔值
      $res = [];  // 通常使用外部变量将结果返回, 本例返回二维数组
      // 必须先将表中记录根据主键排序后, 分块将数据以对象数组方式注入到回调参数$staffss中
      // 回调以引用的方式使用了外部变量$res, 所以,函数中对$res的更新将会实时映射到原数组中
      $res = DB::table('staffs')->orderBy('id')->chunk(5, function ($staffss) use (&$res){
        foreach ($staffss as $staffs) {
          $res[] = ['name'=>$staffs->name, 'email'=>$staffs->email];
        }
      });
    
      dump($res);
    }

    2.2 新增记录

    • insert(array $data): 新增记录,返回新增的记录数量
    • insertGetId(array $data): 新增记录,关返回新增记录的主键ID
    // 新增一条记录,数据以一维数组方式提供
    $data = [
        'name'=> '东方不败',
        'sex'=>1,
        'age'=>30,
        'salary'=>8000,
        'email'=>'dfbb@php.cn',
        'mobile'=> '15800448811',
        'password'=>sha1('123456'),
        'created_at'=>time(),
        'updated_at'=>time(),
    ];
    
    Route::get('db/add', function () use ($data) {
      // 新增一条记录
      $num = DB::table('staffs')->insert($data);
        //return '成功新增了: ' .$num . ' 条记录';
    
      // 获取新增记录的主键ID
      $id = DB::table('staffs')->insertGetId($data);
      return '新增记录的主键ID是: ' . $id;
    });
    • 如果想一次新增多条记录, 传入一个二维数组即可
    • 出错返回QueryException, 多条插入时, 会整体中断, 一条也不会添加成功

    2.3 更新记录

    • update(array $data): 更新记录时, 必须要设置更新条件
    • where(): 更新条件, 更多设置技巧 , 在后面课程中再说, 这里仅用到等值查询
    • ``: 自增自减,默认步长为1, 仅针对整数型字段有意义
    Route::get('db/save/{id}', function ($id) {
        $num = DB::table('staffs')->where('id', $id)->update(['salary'=>1989]);
        return '成功更新了: ' .$num . ' 条记录';
    });

    2.4 删除记录

    • delete(): 删除记录, 与更新一样, 也必须设置条件
    Route::get('db/del/{id}', function ($id) {
        $num = DB::table('staffs')->where('id', $id)->delete();
        return '成功删除了: ' . $num . ' 条记录';
    });

    2.5 聚合查询

    • 主要对记录进行统计,或者对数值字段进行求和,求平均值,以及最大/最小值等
    • 聚合查询的返回都是数值型
    • 涉及方法有: count(),sum(),avg(), min(), max()
    $num = DB::

    3. select()查询

    3.1 字段封装

    • select(columns): 设置查询字段名称
    • 参数是字符串列表: select('id','name','email')
    • 参数是字符串数组: select(['id','name','email'])

    3.2 原始列名

    • selectRaw(string): 直接写SQL语句中的原始列名
    • selectRaw('id,name,salary')
    Route::get('db/column', function () {
      $res = DB::table('staffs')->select('id', 'name','email')->get();
      $res = DB::table('staffs')->select('id', 'name as 姓名','email as 邮箱')->get();
      $res = DB::table('staffs')->select(['id', 'name','email'=>'工资'])->get();
      $res = DB::table('staffs')->selectRaw('`id`,`name`, `salary`')->get();
      $res = DB::table('staffs')->selectRaw('`id`,`name`, `salary` as `工资`')->get();
      dump($res);
    });

    4. where() 简单查询

    4.1 表达式查询

    • 语法: where(字段, 操作符, 值), 如where('id', '>', 5)
    • 等值查询可省略操作符: where(字段, 值), 如where('id', 2)

    4.2 区间查询

    • 语法: whereBetween(字段, [起始, 结束])
    • SQL: BETWEEN start AND end

    4.3 集合查询

    • 语法: whereIn(字段, [值1,值2, ...])
    • SQL: WHERE field IN (value1, value2,....)

    4.4 AND 查询

    • 语法: where([...])
    • SQL: WHERE condition1 AND condition2

    4.5 OR 查询

    • 语法: orWhere()
    • SQL: WHERE condition1 OR condition2

    4.6 原始条件查询

    • 语法: whereRaw(SQL)

    4.7 闭包查询

    • 语法: where(function ($query){...})
    • 闭包中直接使用$query调用查询方法, 不需要返回结果

    4.8 其它方法

    • toSql(): 查看生成的SQL语句模板
    • toArray(): 将结果对象集合转为数组
    Route::get('db/where', function (){
    
      // 1. 表达式查询: where(字段, 操作符, 值)
      $res = DB::table('staffs')->where('id', '=', 5)->pluck('name', 'id');
      // 等值查询,中间的等号可以省略
      $res = DB::table('staffs')->where('id', 5)->pluck('name', 'id');
      $res = DB::table('staffs')->where('age', '>', 35)->pluck('name','id');
    
      // 2. 模糊查询
      $res = DB::table('staffs')->where('name', 'like', '%大%')->pluck('name','id');
    
      // 3. 区间查询,还有一个whereNotBetween(),与之功能相反
      $res = DB::table('staffs')->whereBetween('age', [30,40])->pluck('name','id');
    
      // 4. 集合查询
      $res = DB::table('staffs')->whereIn('id', [1,3,5])->pluck('name', 'id');
    
      // 5. AND 查询, "与",只有满足所有条件才会匹配到
      // `age` < 50 AND `salary` < 5000
      $res = DB::table('staffs')
          ->where('age','<', 50)
          ->where('salary', '<', 5000)
          ->pluck('name','id');
      // 可以将多个条件以数组参数形式传给where()
      $res = DB::table('staffs')->where([
          ['age','<', 50],
          ['salary', '<', 5000]
      ])->pluck('name','id');
    
      // 6. OR 查询 , "或", 只要满足一个条件就会匹配到
      // `age` < 30 OR `salary` < 5000
      $res = DB::table('staffs')
          ->where('age','<', 30)
          ->orWhere('salary', '<', 5000)
          ->pluck('name','id');
    
      // 7. 原生条件查询
      // toSql(): 返回生成的SQL语句
      // 使用原生表达式时, 字段名或关键字推荐加上反引号, 防止命名冲突
      $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000')->toSql();
      //  dd($res);
      $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000')->pluck('name', 'id');
      // 设置结果集中的字段的方法select(),也有类似的方法
      $res = DB::table('staffs')->whereRaw('`age` < 30 AND `salary` > 5000')
          ->selectRaw('`id`,`name`,`age`, `salary`')
          ->get();
    
      // 8. 闭包查询: 例如查询年龄小于40,且 工资在5000到8000之间的 的 男性员工信息
      // SELECT * FROM `staffs` WHERE `age` < 40 AND (`salary` BETWEEN 5000 AND 8000 ) AND `sex` =1
      // 对于这样的查询任务, 使用前面的语法实现起来比较繁琐,而使用闭包查询则非常直观方便
      $res = DB::table('staffs')->where('age', '<', 40)
            ->where(function (Illuminate\Database\Query\Builder $query){
                $query->whereBetween('salary', [5000, 8000])
                    ->where('sex', 1);
            })->get();
    
      // 查看结果
      dump($res->toArray());
    });

    5. where()连接查询

    5.1 内连接

    5.1.1 传统多表查询

    • 通过在查询中设置连接条件的方式,来移除结果集中某些数据行后的交叉连接

    • 通俗讲: 利用条件表达式来消除交叉连接的某些行

    • 换句话说, 就是在交叉连接(迪卡尔乘积)的结果集中返回满足条件的记录

    • 内连接与传统的多表查询是类似的, 例如有二张表,通过id关联

    -- 员工表staffs(id,name,salary...), 客户表custom(id,name,sid)
    -- staffs.id 与 custom.sid 关联
    -- 将客户表中的名称与对应的员工关联,典型的多表等值查询
    SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
    FROM `staffs`, `custom`
    WHERE `custom`.`sid` = `staffs`.`id`

    5.1.2 内连接多表查询

    • 内连接:

      • INNER JOIN: 连接二张表
      • ON: 设置连接条件
    • 将以上SQL语句改为内连接查询, 需要更新二处:

      1. FROM 后二张表之间用: INNER JOIN 连接
      2. WHERE换成: ON (不换成ON也不报错)
    SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
    FROM `staffs` INNER JOIN `custom`
    ON `custom`.`sid` = `staffs`.`id`
    • 尽管以上二种语法都可以实现多表查询,但内连接更加直观高效,推荐使用
    • 内连接是SQL默认的连接方式,所以INNER关键字可省略,只用JOIN即可
    • 内连接最常用于二张表,但也适合于二张以上的表执行查询

    5.2 外连接

    与内连接在是交叉集合中返回匹配记录不同, 外连接更注意二张表之间的关系
    外连接中的表之间的顺序会影响到查询结果
    所以按照连接表的顺序,可以分为: 左外连接 和 右外连接 二类

    5.2.1 术语

    • 基表: 由它发起连接请求, 返回结果的行数与它一致
    • 参考表: 与基表中的条件进行匹配, 不匹配的返回NULL
    • 所以, 根据基表在左边还是右边, 分为"左连接"和"右连接"二种形式

    5.2.2 左外连接

    • 左边是基表, 右边是参考表
    • 以左边为基表进行查询, 返回的是左表中的所有行
    • 结果集中, 除了匹配的行之外, 还包括左表中存在但与右边中不匹配的行
    • 未匹配的行中, 对应列设置为空值NULL, 表示右边中这些行没有与左表中匹配的行
    • 表之间使用连接关键字: LEFT OUTER JOIN 或者 LEFT JOIN
    -- 以左边custom为基表, 右边staffs员工表为参考表, 查询每个客户对应的负责的员工
    -- 凡是与客户表无对应的员工, 全部在结果中返回null, null值出现在左边
    SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
    FROM `staffs`
    LEFT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`

    5.2.3 右外连接

    • 右边是基表, 左表是参考表
    • 以右表为基表进行查询, 返回的是右表中的所有行
    • 与左连接一样, 未匹配的行,对应的列设置为NULL值
    • 使用的连接关键字: RIGHT OUTER JOIN 或者 RIGHT JOIN
    SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
    FROM `staffs`
    RIGHT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`

    5.2.4 交叉连接

    • 又叫迪卡尔乘积连接, 不设置where条件返回左边行数乘以右边行数之积
    SELECT `custom`.`id`, `custom`.`name`, `staff`.`name`
    FROM `staff`
    CROSS JOIN `custom`
    • 如果设置条件, 返回结果与前面的内连接等值查询完全相同
    • 所以, 内连接就是在交叉连接的结果集中选取匹配的记录
    --带条件
    SELECT `custom`.`id`, `custom`.`name`, `staff`.`name`
    FROM `staff`
    CROSS JOIN `custom`
    WHERE `custom`.`sid` = `staff`.`id`;

    5.2.4 小提示

    • 左/右连接,最终返回的结果集记录数量与以哪个为基表相关
    • 匹配到行, 对应的列有值, 未匹配到行, 对应的列为NULL值
    • 条件交叉连接, 其实就是内连接

    5.3 Laravl中的实现

    • 内连接: join(参考表, 参考表字段, 操作符, 基表字段)
    • 外左连接: leftJoin(参考表, 参考表字段, 操作符, 基表字段)
    • 外右连接: rightJoin(参考表, 参考表字段, 操作符, 基表字段)
    • 交叉连接: crossJoin(参考表, 参考表字段, 操作符, 基表字段)
    // 连接查询
    Route::get('db/join', function (){
        // 内连接
        $res = DB::table('custom')
            ->join('staffs', 'staffs.id', '=' ,'custom.sid')
            ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile')
            ->get();
    
        // 左连接
        // 返回左表custom匹配到的所有行,staffs未匹配行的字段为NULL值
        $res = DB::table('custom')
            ->leftJoin('staffs', 'staffs.id', '=' ,'custom.sid')
            ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile')
            ->get();
    
        // 右连接
        // 返回右表staffs匹配到的所有行, custom未匹配行的字段为NULL值
        $res = DB::table('custom')
            ->rightJoin('staffs', 'staffs.id', '=' ,'custom.sid')
            ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile')
            ->get();
    
        // 交叉连接(无条件, 返回二张表行数乘积6*13=78, 也叫迪卡尔乘积)
        $res = DB::table('custom')
            ->crossJoin('staffs')
            ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile')
            ->get();
    
        // 交叉连接(有条件, 从交叉结果集是筛选匹配条件的行, 也内连接查询完全相同)
        $res = DB::table('custom')
            ->crossJoin('staffs','staffs.id', '=' ,'custom.sid')
            ->select('custom.id', 'custom.name as custom_name', 'staffs.name', 'staffs.mobile')
            ->get();
    
        // 查看结果
        dump($res);
    });

    下节课, 我们一起来学习激动人心的Eloquent模型操作数据表

  10. suhao commented on May 10, 2022

    @suhao
    MemberAuthor

    Eloquent模型

    1. Eloquent 是什么

    1.1 ORM与ActiveRecord的关系

    • Eloquent: 是一个 "ActiveRecord ORM" 框架
    • ORM: 对象关系映射模型(Object Relational Mapping)
    • ORM, 主要用来构建数据表与类之间的映射关系,实现以编程方式处理数据库
    • ActiveRecord是ORM的一种实现方案
    • Eloquent则是Laravel版的ActiveRecord

    1.2 Eloquent与数据表的关系

    • 一个Eloquent模型类会映射到一张数据表
    • 模型类的实例, 通常会有二个主要的功能:
      • 与表中的一条具体记录对应, 用来操作该记录
      • 用来调用模型中的方法, 对数据表进行操作(如CURD)
    • 使用模型操作数据表时, 遵循"约定优于配置"原则
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions