walklang / php.gl

0 stars 0 forks source link

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

Open suhao opened 2 years ago

suhao commented 2 years ago

Laravel源码剖析

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

suhao commented 2 years ago

目录结构与配置

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

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

  1. 环境配置
  1. 数据库

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

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

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

suhao commented 2 years ago

MVC流程分析

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

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

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

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

2. 控制器

3. 模型

4. 视图

suhao commented 2 years ago

中间件

1. 中间件执行流程

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


2. 中间件创建

# 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');
suhao commented 2 years ago

路由

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. 路由参数

2.1 必选参数

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

Route::get('sum/{a}/{b}', function ($a, $b) {
  return "$a + $b = " . ($a + $b);
});

2.2 可选参数

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 参数类型约束(了解即可)

Route::get('sum/{a}/{b}', function ($a, $b) {
  return "$a + $b = " . ($a + $b);
})->where('a', '\d+')->where('b', '\d+');
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. 命名路由

// 为当前路由'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>';
});
Route::get('res', function (){
    $url =url('sum',[100,200]);
    return '<a href="'.$url.'">计算</a>';
});
// 执行结果与上面是完全一样的

4. 闭包路由

Route::get('dev', function () { return 'Hello dev.tt';});
Route::get('dev', 'hello');
function hello { return 'Hello dev.tt'; }

5. 控制器路由

namespace App\Http\Controllers;

class DemoController extends Controller
{
    public function index()
    {
      //...
    }
}

suhao commented 2 years ago

控制器

1. 控制器与MVC

1.1 控制器简介

1.2 控制器与MVC

mvc

1.3 控制器的功能


2. 控制器的创建

2.1 控制器命名空间与基类

// 默认控制器代码
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';
}
// 原生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>
<!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>
Route::get('staffs', 'StaffController@index');

2.2 获取用户的输入

以添加一个员工数据为例

// 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');
<!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>
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 门面与依赖注入之间的关系

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


3. 资源控制器

3.1 创建资源控制器

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

<?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 创建资源控制器的路由

// 一次性注册全部的UserControler资源控制器中的全部路由
Route::resource('article', 'ArticleController');
请求类型 路由地址 命名路由 控制器方法 描述
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');
public function edit($id)
{
    return '编程文章: ' . route('article.edit', [$id]);
}
// route('命名路由', [参数列表])
// url('路由地址')
suhao commented 2 years ago

视图与参数

1. 视图简介

body {
    background-color: lightblue;
}
// 解释css文件
Route::get('staffs/style', function () {
//    return view('staffs.style');
    return View::make('staffs.style');
});

// 路由仅是渲染一个视图,可以用视图路由进一步简化
Route::view('staffs/style', 'staffs.style');

2. 视图传参

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::get('staffs/show/{id}', 'staffsController@show');
<!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');

suhao commented 2 years ago

Blade模板引擎

1. Blade模板引擎简介


2. 变量显示


3. 流程控制

3.1 条件判断

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

// 演示模板中的流程控制
// 使用Route::view(), 第三个参数是给模板的赋值
Route::view('staffsss/demo1', 'staffsss.demo1', ['grade'=> 77]);
<!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; ?>

3.1.2 @switch () ... @endswitch

@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 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 循环结构

// 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表中所有数据
$staffssss = \App\Models\staffsssModel::all();
//dump($staffssss);

// 将集合类型转为普通的数组,便于在模板中调用
$staffssss = \App\Models\staffsssModel::all()->toArray();
//dump($staffssss);

//创建路由, 模板赋值
Route::view('staffsss/demo2', 'staffsss.demo2', ['staffssss'=>$staffssss]);
<!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. 模板继承

4.1 布局文件(父视图)

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

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

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

4.1.3 @yield@section/@show区别

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 指令--}}

{{虽然子视图与父视图都在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
@section ('css')
    @parent
    .container {
    color: red;
    }
@endsection

4.3 导入组件

4.3.1 导入单个组件/模块

//include导入组件测试
Route::view('include', 'include');
  // 组件: 以添加一个课程按钮为例
  <button>{{ $course }}</button>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>导入组件</title>
</head>
<body>
<h3>你喜欢的课程有哪些?</h3>
@include('button', ['course'=>'PHP'])
</body>
</html>

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

//each循环导入组件测试
Route::view('each', 'each', ['courses'=>['php','javascript','python']]);
<h3 style="color: red">空空如也~~</h3>
<!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>

4.3 插槽与组件

<script>
    alert('{{ $slot }}')
</script>
// component 测试
Route::view('component', 'component');
@component('alert')
    验证通过
@endcomponent

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

suhao commented 2 years ago

数据库操作

1. 原生查询

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

1.1 原生读/查询操作

// 获取指定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字符串

// 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 原生更新操作

// 更新指定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 原生新增操作

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 原生删除操作

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 查询记录

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 新增记录

// 新增一条记录,数据以一维数组方式提供
$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;
});

2.3 更新记录

Route::get('db/save/{id}', function ($id) {
    $num = DB::table('staffs')->where('id', $id)->update(['salary'=>1989]);
    return '成功更新了: ' .$num . ' 条记录';
});

2.4 删除记录

Route::get('db/del/{id}', function ($id) {
    $num = DB::table('staffs')->where('id', $id)->delete();
    return '成功删除了: ' . $num . ' 条记录';
});

2.5 聚合查询

$num = DB::

3. select()查询

3.1 字段封装

3.2 原始列名

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 表达式查询

4.2 区间查询

4.3 集合查询

4.4 AND 查询

4.5 OR 查询

4.6 原始条件查询

4.7 闭包查询

4.8 其它方法

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 传统多表查询

-- 员工表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 内连接多表查询

SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
FROM `staffs` INNER JOIN `custom`
ON `custom`.`sid` = `staffs`.`id`

5.2 外连接

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

5.2.1 术语

5.2.2 左外连接

-- 以左边custom为基表, 右边staffs员工表为参考表, 查询每个客户对应的负责的员工
-- 凡是与客户表无对应的员工, 全部在结果中返回null, null值出现在左边
SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
FROM `staffs`
LEFT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`

5.2.3 右外连接

SELECT `custom`.`id`, `custom`.`name`, `staffs`.`name`
FROM `staffs`
RIGHT JOIN `custom` ON `custom`.`sid` = `staffs`.`id`

5.2.4 交叉连接

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 小提示

5.3 Laravl中的实现

// 连接查询
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模型操作数据表

suhao commented 2 years ago

Eloquent模型

1. Eloquent 是什么

1.1 ORM与ActiveRecord的关系

1.2 Eloquent与数据表的关系