Laravel 6.0

Laravel 学习交流 QQ 群:375462817

本记录文档前言

Laravel 文档写的很好,只是新手看起来会有点吃力,需要结合经验和网上的文章,多读、细读才能更好的理解。多读、细读官方文档!!!本文类似于一个大纲,欲知其中详情,且去细读官方文档:Laravel 6.0 docs。

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

第一章:前言(一、发布注意事项。二、升级指导。三、贡献指南。四、api 文档。)

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

一、发布注意事项

Laravel 6.0 是 LTS 版本,生产日期,2019 年 09 月。提供两年的 bug 修复和三年的安全修复。非 LTS 版本,只提供六个月的 bug 修复和一年的安全修复。

二、升级指导

https://laravel.com/docs/6.0/upgrade

三、贡献指南

https://laravel.com/docs/6.0/contributions

四、api 文档

官方 api 文档地址:https://laravel.com/api/6.0/


#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

第二章:开始(一、安装。二、配置。三、目录结构。四、homestead。五、Valet。六、部署。)

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

一、安装

服务器需求

PHP >= 7.2.0
BCMath PHP Extension
Ctype PHP Extension
JSON PHP Extension
Mbstring PHP Extension
OpenSSL PHP Extension
PDO PHP Extension
Tokenizer PHP Extension
XML PHP Extension

两种安装方式

  1. composer global require "laravel/installer"
    laravel new blog // 无法指定版本
  2. composer create-project --prefer-dist laravel/laravel blog "6.0.*"

安装须知

  1. 根目录是public
  2. 必须设置 web 服务器可读写storagebootstrap/cache目录及其子目录
  3. 在 web 服务器配置中设置优雅链接 (即去除index.php
  4. php artisan key:generate
  5. mv .env.example .env

web 服务器上的配置

# apache
# 打开 mod_rewrite 模块
# public/.htaccess
Options +FollowSymLinks -Indexes
RewriteEngine On

RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
# nginx
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

二、配置

配置目录 config

环境配置

  1. .env 文件内的变量会被系统级别或服务器级别的变量覆盖。
  2. 有空格的值请用双引号包含起来 APP_NAME="My Application"
  3. .env 文件内的变量通过env()函数获取,config目录下的变量通过config()函数获取。
  4. 在运行PHPUnit测试时或执行以--env=testing为选项Artisan命令时,.env.testing会覆盖 .env 文件中的值。
$environment = App::environment();

if (App::environment('local')) {
    // 环境是 local
}

if (App::environment(['local', 'staging'])) {
    // 环境是 local 或 staging...
}

隐藏 debug 页面中的环境变量

# config/app.php
return [

    // ...

    'debug_blacklist' => [
        '_ENV' => [
            'APP_KEY',
            'DB_PASSWORD',
        ],

        '_SERVER' => [
            'APP_KEY',
            'DB_PASSWORD',
        ],

        '_POST' => [
            'password',
        ],
    ],
];

操作配置、缓存配置

$value = config('app.timezone');                           // 获取
config(['app.timezone' => 'America/Chicago']);             // 设置
php artisan config:cache                 // 缓存(执行该命令后 env 函数将会失效)
php artisan config:clear                 // 清除 

维护模式

维护模式 === 503 === Service Unavailable

php artisan down                                           
php artisan down --message="Upgrading Database" --retry=60 
// 优先加载:resources/views/errors/503.blade.php

# 5.5 版本的时候,尝试第二条命令的时候,你会发现并不能生效,一开始我也以为是个 bug,不知道更改没有
# 为此我还在 github 上 pull request
# 官方解释说你要自建模板 resources/views/errors/503.blade.php 去实现任何你想实现的功能。
# 我觉得这边做的不是很好,既然有这个命令选项,就应该实现对应的功能,
# 否则新手根据文档学习到的时候,会困惑为什么该指令的参数不能生效。

php artisan down --allow=127.0.0.1 --allow=192.168.0.0/16
php artisan up

当应用程序处于维护模式时,不会处理队列任务。退出维护模式后会继续处理。

三、目录结构

根目录下的各个目录

  1. app:应用程序核心目录,几乎项目所有的类都在这里。
  2. bootstrap:包含框架启动文件 app.php,和启动时为了优化性能而生成的文件。
  3. config:包含所有配置文件。最好是读一遍这些文件,了解你可以轻松配置哪些内容。
  4. database:包含数据库填充、迁移、模型工厂文件。可以用作 SQLite 数据库存放目录。
  5. public:静态资源目录,并包含了首页文件 index.php
  6. resource:包含了未编译的源文件(模板、语言、资源)。
  7. routes:包含了所有的路由定义。
  8. storage:包含了编译好的模板文件,session 文件,缓存文件,日志等文件。
  9. tests:包含了自动测试文件。运行测试命令 php vendor/bin/phpunit
  10. vendorcomposer 依赖目录。

app目录下的各个目录

app 目录下的很多目录是命令生成的。由 Composer 使用 PSR-4 自动加载标准自动加载。
查看生成命令:php artisan make:list

  1. Broadcasting:包含所有 broadcast channel 类。
  2. Console:包含自定义的命令和用来注册命令、定义计划任务的内核文件。
  3. Events:事件目录。
  4. Exceptions:异常和异常处理目录。
  5. Http:包含了控制器、中间件和表单请求。几乎所有请求的处理逻辑都被放在这里。
  6. Jobs:储存队列任务。
  7. Listeners:存储事件的监听。
  8. Mail:存储邮件类目录。
  9. Notifications:存放通知类。laravel 内置了很多驱动: email, Slack, SMS, database。
  10. Policies:授权策略类目录。
  11. Providers:包含了所有服务提供者。服务提供者通过在服务容器上绑定服务、注册事件或者执行其他任务来启动你的应用。
  12. Rules:储存自定义验证规则对象。

routes目录下的各个目录

  1. web.php内的路由将应用 web 中间件组(功能:session 状态,CSRF 保护,cookie 加密等)。
  2. api.php内的路由将应用 api 中间件组(功能:访问速率控制等)。所有的请求将通过 token 认证,无 session 状态。
  3. consoles.php定义了所有基于控制台命令的闭包。
  4. channels.php注册了所有的事件广播频道。

四、Homestead(TODO)

本节介绍了 homestead 是什么,怎么用。

介绍

Homestead 是 Laravel 官方提供的 vagrant box。
vagrant 一个用来给虚拟机提供物品(box)的容器,放在容器(vagrant)里的东西被称作 box 。box 一般就是一个操作系统的镜像。

Homestead 提供了 ubantu、git、php、nginx、apache、mysql、mariadb、sqlite3、postgreSQL、composer、node(With Yarn, Bower, Grunt, and Gulp)、redis、memcached、beanstalkd、mailhog、elasticsearch、ngrok。

windows 需要在 BIOS 中开启硬件虚拟功能 (VT-x)。如果在 UEFI 上使用 Hyper-V,为了使用 VT-x 需要禁用 Hyper-V。

安装

  1. 安装虚拟机和 vagrant。
    (建议使用 virtualbox,vmware 收费并需要购买插件,parallels 也需要插件,Hyper-V 有局限性)
    vagrant 下载页
    virtualbox 下载页
  2. 往 vagrant 里面放 laravel/homestead box。
    vagrant box add laravel/homestead

国内墙的厉害,需要下载下来再进行本地安装: 
https://vagrantcloud.com/laravel/boxes/homestead/versions/x.x.x/providers/virtualbox.box
在上面链接中找到想要下载的版本,然后替换掉 x.x.x ,复制到迅雷中进行下载。
重命名为 larabox
vagrant box add laravel/homestead ./larabox

  1. 克隆仓库 laravel/homestead。(用来初始化 vagrant,配置 homestead box 的程序。)
    a. git clone https://github.com/laravel/homestead.git ~/Homestead
    b. cd ~/Homestead
    c. git checkout v7.1.2(切换到稳定版本)
    d. bash init.sh或者init.bat(生成 Homestead.yaml 文件)

配置(配置文件 Homestead.yaml)

指定虚拟机

可以是 virtualbox, vmware_fusion, vmware_workstation, parallels 或者 hyperv
provider: virtualbox

共享文件夹

folders属性用于配置本地和homestead环境的同步文件夹。
由于多站点或项目有太多文件而导致性能变差时候,可以设置不同的文件夹来同步不同的项目。
NFS也可以用来优化性能,windows 不支持。使用NFS时,安装vagrant-bindfs插件可维护正确的文件和文件夹权限。
利用 options 键可以设置其他选项。

nginx 站点

homestead使用sites属性让你轻松配置nginx域名。使用vagrant reload --provision更新虚拟机中的nginx配置。
配置好域名,记得在本地 hosts 文件添加解析记录。例如:192.168.10.10 homestead.test

启动和删除 box
# 修改 homestead.rb 
# settings[“version”] ||= “>= 0”  
# 找到 composer self-update 删除它 
vagrant up
vagrant destroy --force
为每个项目安装 homestead

这样你就可以在其他项目目录内,通过 vagrant up 来进行该项目的工作。

composer require laravel/homestead --dev
// make 命令自动生成 Vagrantfile 和 Homestead.yaml
php vendor/bin/homestead make    // linux & mac
vendor\\bin\\homestead make         // windows
安装 mariadb
box: laravel/homestead
ip: "192.168.10.10"
memory: 2048
cpus: 4
provider: virtualbox
mariadb: true
安装 elasticsearch

需要指定版本,默认安装将创建一个名为 ‘homestead’ 的集群,确保你的虚拟机内存是elasticsearch的两倍。

box: laravel/homestead
ip: "192.168.10.10"
memory: 4096
cpus: 4
provider: virtualbox
elasticsearch: 6
bash命令别名
alias ..='cd ..'      // 随后记得 vagrant reload --provision

日常用法

全局访问 homestead

mac 或 linux

function homestead() {
    ( cd ~/Homestead && vagrant $* )
}

windows

@echo off

set cwd=%cd%
set homesteadVagrant=C:\Homestead

cd /d %homesteadVagrant% && vagrant %*
cd /d %cwd%

set cwd=
set homesteadVagrant=

替换C:\Homestead,再将脚本加入环境变量即可。

设置完成后可以在任何地方使用 homestead uphomestead ssh

连接数据库

mysql 127.0.0.1 33060
postgreSQL 127.0.0.1 54320
在虚拟机里面连接数据库的端口还是默认值 33065432
账号密码:homestead / secret

添加多个网站
sites:
    - map: homestead.test
      to: /home/vagrant/code/Laravel/public
    - map: another.test
      to: /home/vagrant/code/another/public

192.168.10.10  homestead.test
192.168.10.10  another.test
添加 nginx 其他配置
sites:
    - map: homestead.test
      to: /home/vagrant/code/Laravel/public
      params:
          - key: FOO
            value: BAR
配置时间任务

如果需要开启 artisan 命令schedule:run一样的效果,配置如下
schedule:run命令的原理是每分钟都会去检查 App\Console\Kernel 类中是否有任务需要执行,有则执行。)

sites:
    - map: homestead.test
      to: /home/vagrant/code/Laravel/public
      schedule: true
配置 Mailhog(.env)

Mailhog 用来捕获你的邮件,并检查它。你的邮件并不会真的发出去。

MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
端口配置

默认:

SSH: 2222 → Forwards To 22
ngrok UI: 4040 → Forwards To 4040
HTTP: 8000 → Forwards To 80
HTTPS: 44300 → Forwards To 443
MySQL: 33060 → Forwards To 3306
PostgreSQL: 54320 → Forwards To 5432
Mailhog: 8025 → Forwards To 8025

自定义:

ports:
    - send: 50000
      to: 5000
    - send: 7777
      to: 777
      protocol: udp
分享你的环境

vagrant share 但是如果Homestead.yaml配置了多个站点,此命令不再支持。
解决方案,使用 homestead 内置命令:

vagrant ssh
share homestead.test

vagrant 天生就不安全,当你使用 share 命令,就会在互联网中暴露的你虚拟机。

切换 php 版本

只兼容 nginx

切换 web 服务器

apache 和 nginx 可以同时安装,但不可同时运行,原理应该是抢占 80 端口。
运行命令 flip 可以关闭其中一个,开启另一个。

配置网络接口

可以配置任意数量的接口:

networks:
    - type: "private_network"
      ip: "192.168.10.20"

想启用 桥接 接口,请配置 bridge 设置,并将网络类型更改为 public_network :

networks:
    - type: "public_network"
      ip: "192.168.10.20"
      bridge: "en1: Wi-Fi (AirPort)"

要启用 DHCP,只需从配置中删除 ip 选项:

networks:
    - type: "public_network"
      bridge: "en1: Wi-Fi (AirPort)"
更新 homestead
vagrant box update
git pull origin master

或者

vagrant box update
composer update      # 确保 composer.json 包含 "laravel/homestead": "^7"

如果 windows 下符号链接不生效,添加以下代码块到 Vagrantfile

config.vm.provider "virtualbox" do |v|
    v.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"]
end

五、Valet

https://laravel.com/docs/6.0/valet

六、部署

本节介绍了几个部署要点。

nginx 部署

server {
    listen 80;
    server_name example.com;
    root /example.com/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

部署优化

composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
# 开启 OpCache
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

第三章:架构(一、生命周期。二、服务容器。三、服务提供者。四、facades。五、contracts。)

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

一、请求生命周期

本节主要概括了框架运行的生命周期。

  1. 所有请求必定首先通过 public/index.php
  2. 在上述这个文件中首先加载 composer 自动加载文件,然后从 bootstrap/app.php 实例化一个服务容器(服务容器,顾名思义是一个容器,可以比作是一个药箱,药箱当然要放药了,药就是所谓的服务提供者。在启动时候当然不能把框架里的所有的药都加载进来,只能加载基础的药。所以这一步还加载了一些基础的服务提供者。)。
  3. 接下来,框架会根据请求类型传送请求至 app/Http/Kernel.php 或者 app/Console/Kernel.php
  4. app/Http/Kernel.php扩展了Illuminate\Foundation\Http\Kernel类,父类强制在处理请求前应该做哪些操作,操作内容都放到了 bootstrappers数组里面(配置错误处理、配置记录日志、检测应用环境、注册和启动服务提供者等)。子类在数组middleware中规定了请求在被处理前必须经过的一些处理(读写session、判断是否处于维护模式、验证 csrf 令牌等)。
  5. 实例化Http Kernel,处理请求,返回响应内容。请求将作为参数传入handle方法,返回值就是响应内容。

应用程序的所有服务提供程序都在 config/app.php 配置文件的 providers 数组中配置。首先,将在所有提供程序上调用register方法,然后,一旦所有提供程序都已注册,将调用 boot 方法。一旦应用程序被引导,Request 将被传递给 router 以进行分派。 router 会将请求分派给路由或控制器,以及运行任何路由特定的中间件。

二、服务容器

本节主要讲了服务容器中的绑定,解析,解析事件(类似于在药瓶中放药,拿药,拿药时会发生什么)。

如果不依赖任何接口,不需要指示容器如何构建这些对象,因为它可以使用反射自动解析这些对象。

绑定

基础绑定

$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});  

绑定单例

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});   

绑定实例

$api = new HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\API', $api);   

绑定实例时给定初始化数据

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);     // 利用上下文给绑定设置初始数据

绑定接口到实例

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);     

根据上下文提供不同的绑定

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when(VideoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

给绑定设置标签

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

Service 已经被解析,extend 方法可以用来修改解析出来的实例 $service

$this->app->extend(Service::class, function($service) {
    return new ModifyClass($service);
});

解析

基础解析

$api = $this->app->make('HelpSpot\API');

无法访问 $app 时,这样解析

$api = resolve('HelpSpot\API');
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);      // 解析时候通过关联数组注入依赖

类型提示解析(最常用)

public function __construct(UserRepository $users)

# laravel 实现了 PSR-11 接口,所以就可以用该接口的类型提示解析
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
    $service = $container->get('Service');
    //
});

容器事件

容器解析任何对象时调用

$this->app->resolving(function ($object, $app) {
    
});

容器解析HelpSpot\API时调用

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    
});

三、服务提供者

加载服务提供者是框架启动的关键步骤之一,他们负责启动不同的组件(数据库、队列、验证、路由等),服务提供者被配置在 config/app.php中的providers数组。请注意,其中许多是“延迟”提供程序,这意味着它们不会在每个请求中加载,而是仅在实际需要它们提供的服务时加载。首先,他们的register方法会被调用,全部调用结束,才会依次调用他们的boot方法。当所有服务提供者都注册好了,Request将会被 router 分发到路由或控制器,同时运行路由指定的中间件。

制作一个服务提供者

  1. php artisan make:provider RiakServiceProvider
  2. 服务提供者主要由两个方法:registerbootregister 只负责绑定一些东西到容器。boot 可以使用类型提示解析等来完成任意你想做的事情,这些都归功于容器调用所有服务提供者的register方法之后才去调用boot方法。
  3. config/app.phpproviders数组中注册服务提供者。

boot 方法在所有服务提供者 register 调用完毕后执行。

制作一个延迟服务提供者

如果只是绑定服务到服务容器,可以选择将该服务提供者设置为延迟(实现 DeferrableProvider 接口)。

class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection($app['config']['riak']);
        });
    }

    public function provides()
    {
        return [Connection::class];
    }
}

四、Facades

原理

其实facades就是各种别名。当你使用某个facade的静态方法时,会触发它的父类的__callStatic方法,该方法会找到注册在容器中的facade原类名,最终调用原类名中的对应方法。

不要在一个类中,用太多的facades。过于臃肿的情况下应该将大类分解成几个小类。

优点

方便测试(辅助函数和 facades 没什么区别,测试方法也是一样的)。

Route::get('/cache', function () {
    return Cache::get('key');     // === return cache('key');
});
public function testBasicExample()
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $this->visit('/cache')
         ->see('value');
}

实时的 facades

原生用法 vs 实时用法

# 原生用法
use App\Contracts\Publisher;
public function publish(Publisher $publisher)
{
    $this->update(['publishing' => now()]);
    $publisher->publish($this);
}
# 实时用法
use Facades\App\Contracts\Publisher;

Publisher::publish($this);

测试实时的 facades

use Facades\App\Contracts\Publisher;
Publisher::shouldReceive('publish')->once()->with($podcast);

facades 列表

Facade Class Service Container Binding
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection db.connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue  
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFactory  
Response (Instance) Illuminate\Http\Response  
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder  
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator  
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View

五、Contracts

FacadesContracts 没有什么值得注意的区别,但是当你开发第三方包的时候,最好使用 Contracts,这样有利于你编写测试,否则如果使用 Facades,因为是第三方包,将不能访问 facade 测试函数。

使用方法

在构造函数中类型提示注入就行了。

Contracts 列表

Contract References Facade
Illuminate\Contracts\Auth\Access\Authorizable  
Illuminate\Contracts\Auth\Access\Gate Gate
Illuminate\Contracts\Auth\Authenticatable  
Illuminate\Contracts\Auth\CanResetPassword  
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\Guard Auth::guard()
Illuminate\Contracts\Auth\PasswordBroker Password::broker()
Illuminate\Contracts\Auth\PasswordBrokerFactory Password
Illuminate\Contracts\Auth\StatefulGuard  
Illuminate\Contracts\Auth\SupportsBasicAuth  
Illuminate\Contracts\Auth\UserProvider  
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Bus\QueueingDispatcher Bus::dispatchToQueue()
Illuminate\Contracts\Broadcasting\Factory Broadcast
Illuminate\Contracts\Broadcasting\Broadcaster Broadcast::connection()
Illuminate\Contracts\Broadcasting\ShouldBroadcast  
Illuminate\Contracts\Broadcasting\ShouldBroadcastNow  
Illuminate\Contracts\Cache\Factory Cache
Illuminate\Contracts\Cache\Lock  
Illuminate\Contracts\Cache\LockProvider  
Illuminate\Contracts\Cache\Repository Cache::driver()
Illuminate\Contracts\Cache\Store  
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Console\Application  
Illuminate\Contracts\Console\Kernel Artisan
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactory Cookie::queue()
Illuminate\Contracts\Database\ModelIdentifier  
Illuminate\Contracts\Debug\ExceptionHandler  
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud Storage::cloud()
Illuminate\Contracts\Filesystem\Factory Storage
Illuminate\Contracts\Filesystem\Filesystem Storage::disk()
Illuminate\Contracts\Foundation\Application App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Http\Kernel  
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Mail\Mailable  
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Notifications\Dispatcher Notification
Illuminate\Contracts\Notifications\Factory Notification
Illuminate\Contracts\Pagination\LengthAwarePaginator  
Illuminate\Contracts\Pagination\Paginator  
Illuminate\Contracts\Pipeline\Hub  
Illuminate\Contracts\Pipeline\Pipeline  
Illuminate\Contracts\Queue\EntityResolver  
Illuminate\Contracts\Queue\Factory Queue
Illuminate\Contracts\Queue\Job  
Illuminate\Contracts\Queue\Monitor Queue
Illuminate\Contracts\Queue\Queue Queue::connection()
Illuminate\Contracts\Queue\QueueableCollection  
Illuminate\Contracts\Queue\QueueableEntity  
Illuminate\Contracts\Queue\ShouldQueue  
Illuminate\Contracts\Redis\Factory Redis
Illuminate\Contracts\Routing\BindingRegistrar Route
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFactory Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Routing\UrlRoutable  
Illuminate\Contracts\Session\Session Session::driver()
Illuminate\Contracts\Support\Arrayable  
Illuminate\Contracts\Support\Htmlable  
Illuminate\Contracts\Support\Jsonable  
Illuminate\Contracts\Support\MessageBag  
Illuminate\Contracts\Support\MessageProvider  
Illuminate\Contracts\Support\Renderable  
Illuminate\Contracts\Support\Responsable  
Illuminate\Contracts\Translation\Loader  
Illuminate\Contracts\Translation\Translator Lang
Illuminate\Contracts\Validation\Factory Validator
Illuminate\Contracts\Validation\ImplicitRule  
Illuminate\Contracts\Validation\Rule  
Illuminate\Contracts\Validation\ValidatesWhenResolved  
Illuminate\Contracts\Validation\Validator Validator::make()
Illuminate\Contracts\View\Engine  
Illuminate\Contracts\View\Factory View
Illuminate\Contracts\View\View View::make()
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

第四章:基础(一、路由。二、中间件。三、CSRF 保护。四、控制器。五、请求。六、响应。七、视图。八、Url 生成。九、Session 。十、验证。十一、错误与日志)

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

一、路由

web/api.php 中定义的路由会自动添加api前缀,如需修改该前缀,可以在RouteServiceProvider 修改。
web.php 路由里的 POST, PUT, DELETE 方法,在提交表单时候必须加上CSRF参数。

@csrf ...

两个 api Router 和 Route

resource

* GET /test
index()  //  展示列表

* GET /test/create
create()  // 展示用来创建的表单

* POST /test
store()  // 增加资源

* GET /test/{id}
show($id)  // 展示一个资源

* GET /test/{id}/edit
edit($id)  // 展示编辑表单

* PUT /test/{id}
update($id)  // 更新特定资源

* DELETE /test/{id} 
destroy($id)  // 移除资源

基本路由

Route::get($uri, $callback);
Route::post($uri, $callback);
Route::put($uri, $callback);        // 全体更新
Route::patch($uri, $callback);      // 局部更新
Route::delete($uri, $callback);
Route::options($uri, $callback);    // 允许客户端检查性能

Route::any($uri, $callback);        // 任意 method

Route::match(['get', 'post'], '/', function () {
    //
});
# 重定向路由
Route::permanentRedirect('/here', '/there');  // 301
Route::redirect('/here', '/there', 301);  // 第三个参数不写则默认为 302

# 只需要返回一个视图
Route::view('/welcome', 'welcome');
Route::view('/welcome', 'welcome', ['name' => 'Taylor']);

路由参数

Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

Route::get('user/{name?}', function ($name = 'John') {   // 一定要给可选参数设置默认值
    return $name;
});

正则表达式约束


Route::get('user/{id}', function ($id) {
    //
})->where('id', '[0-9]+');

Route::get('user/{id}/{name}', function ($id, $name) {
    //
})->where(['id' => '[0-9]+', 'name' => '[a-z]+']);

全局约束

// RouteServiceProvider
public function boot()
{
    Route::pattern('id', '[0-9]+');

    parent::boot();
}

命名路由

Route::get('user/profile', function () {
    //
})->name('profile');

// 使用
$url = route('profile');
// 生成重定向...
return redirect()->route('profile');


Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = route('profile', ['id' => 1]);
// 在中间件中检查当前路由
public function handle($request, Closure $next)
{
    if ($request->route()->named('profile')) {
        //
    }
    return $next($request);
}

添加中间件

Route::middleware(['first', 'second'])->group(function () {
    Route::get('/', function () {
        // 使用 `first` 和 `second` 中间件
    });

    Route::get('user/profile', function () {
        // 使用 `first` 和 `second` 中间件
    });
});

命名空间

Route::namespace('Admin')->group(function () {
    // 在 "App\Http\Controllers\Admin" 命名空间下的控制器
});

子域名路由

Route::domain('{account}.myapp.com')->group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});

路由前缀

Route::prefix('admin')->group(function () {
    Route::get('users', function () {
        // 匹配包含 "/admin/users" 的 URL
    });
});

路由命名前缀

Route::name('admin.')->group(function () {
    Route::get('users', function () {
        // Route assigned name "admin.users"...
    })->name('users');
});

表单伪造

<input type="hidden" name="_method" value="PUT">
// 或者 @method('PUT')

获取当前路由信息

// Route::get('/', 'TestController@test')->name("mytest");
$route = Route::current(); //  object(Illuminate\Routing\Route)
$name = Route::currentRouteName(); // mytest 
$action = Route::currentRouteAction(); // 控制器中:App\Http\Controllers\TestController@test  路由中:null

隐式绑定

Route::get('api/users/{user}', function (App\User $user) {
    return $user->email;	// 传人的 id
});

# 自定义键名 在模型中修改
# App/User.php
public function getRouteKeyName()
{
    return 'slug';
}

显式绑定

# RouteServiceProvider
public function boot()
{
    parent::boot();

    Route::model('user', App\User::class);
}

Route::get('profile/{user}', function ($user) {
    //
});

自定义解析逻辑

public function boot()
{
    parent::boot();

    Route::bind('user', function ($value) {
        return App\User::where('name', $value)->first() ?? abort(404);
    });
}
public function resolveRouteBinding($value)
{
    return $this->where('name', $value)->first() ?? abort(404);
}

默认路由,一般用来 404,一定要放在所有路由最后面

Route::fallback(function () {
    //
});

速率控制

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

Route::middleware('throttle:10|60,1')->group(function () {
    // 60 次给认证过的用户,10 次给普通用户
});

Route::middleware('auth:api', 'throttle:10|rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

二、中间件

php artisan make:middleware TestMiddleware
注册中间件到 app/Http/Kernel.php $routeMiddleware 中。

所有中间件都通过服务容器解析,因此您可以在中间件的构造函数中键入提示所需的任何依赖项。

# 中间件操作发生请求被处理之前
public function handle($request, Closure $next)
{
    if ($request->age <= 200) {
       return redirect('home');
    }    // Perform action

    return $next($request);
}

# 中间件操作发生请求被处理之后
public function handle($request, Closure $next)
{
    $response = $next($request);
    // Perform action
    return $response;
}

注册全局中间件,就将完整类名写在app/Http/Kernel.php文件中的$middleware数组中。如果是非全局,部分的,可以放在文件的其他数组中。也可以不注册,在需要使用时,引入直接使用。

使用中间件的两种方式

# 常用方式
Route::get('admin/profile', function () {
    
})->middleware('auth');

# 不用注册的方式
use App\Http\Middleware\CheckAge;
Route::get('admin/profile', function () {

})->middleware(CheckAge::class);

如果想应用中间件组,请注册一个关联数组到app/Http/Kernel.php$middlewareGroups中。这样就可以使用时填写该数组的键值就行了。默认情况下,RouteServiceProvider已将中间件组web应用在你的web.php的路由中。

Route::get('/', function () {
    //
})->middleware('web');

Route::group(['middleware' => ['web']], function () {
    //
});

给中间件排序

# app/Http/Kernel.php
protected $middlewarePriority = [
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class,
    \App\Http\Middleware\Authenticate::class,
    \Illuminate\Session\Middleware\AuthenticateSession::class,
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
    \Illuminate\Auth\Middleware\Authorize::class,
];

给 middleware 传参

// role 中间件
public function handle($request, Closure $next, $role)
{
    if (! $request->user()->hasRole($role)) {
        // Redirect...
    }
   return $next($request);
}

Route::put('post/{id}', function ($id) {
    //
})->middleware('role:editor');

terminate 方法调用于将响应发送到浏览器之后。

# terminate 会从服务容器解析一个新的中间件实例。
# 如果你希望 handle 和 terminate 是同一实例,请将这个中间件单例绑定 singleton 到服务容器中。
public function handle($request, Closure $next)
{
    return $next($request);
}

public function terminate($request, $response)
{
    // Store the session data...
}

一旦定义了可终止的中间件,就应该将其添加到 app/Http/Kernel.php 文件中的路由或全局中间件列表中。

三、CSRF 保护

VerifyCsrfToken中间件存在于web中间件组中,它实现了 CSRF 保护。而该中间件组被默认应用在web.php文件中的所有路由。

默认情况下,resources/assets/js/bootstrap.js文件会使用Axios HTTP库注册csrf-token meta 标签的值。如果不使用这个库,需要自己去设置。

指定 uri 不去应用 csrf 保护

  1. 不将路由写入route/web.php
  2. VerifyCsrfToken中间件的排除数组 $except 中添加你的 uri
protected $except = [
    'stripe/*',
    'http://example.com/foo/bar',
    'http://example.com/foo/*',
];

除了可以在验证 post 时作为表单参数传递 csrf ,还可以设置 meta 标签来进行 csrf 保护(如 ajax )。
设置:
ajax 获取:'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')

cookie 中,XSRF-TOKEN的值可以直接设为X-CSRF-TOKEN请求头的值。AngularAxios等会自动执行上述操作。

默认情况下,resources/js/bootstrap.js 文件包含 Axios HTTP 库,它会自动为您发送此文件。

四、控制器

定义控制器可以不去继承Controllers,这样你将不能使用middlewarevalidatedispatch等方法。

如果你的控制器只有一个方法,可以这么玩儿:

# Route::get('user/{id}', 'ShowProfile');
# php artisan make:controller ShowProfile --invokable
public function __invoke($id)
{
     return view('user.profile', ['user' => User::findOrFail($id)]);
}

Route::get('profile', 'UserController@show')->middleware('auth');
控制器的构造函数中的中间件的玩法

$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
$this->middleware(function ($request, $next) {
       return $next($request);
});

Resource 控制器

php artisan make:controller PhotoController --resource
php artisan make:controller PhotoController --resource --model=Photo
php artisan make:controller API/PController --api

Route::resource('photos', 'PhotoController');
Route::apiResource('photo', 'PhotoController');    // 没有 create 和 edit

Route::resource('photos', 'PhotoController')->names([
    'create' => 'photos.build'
]);  // 重命名路由名称

Route::resources([
    'photos' => 'PhotoController',
    'posts' => 'PostController'
]);
Route::resource('photo', 'PhotoController', ['only' => [
    'index', 'show'
]]);
Route::resource('photo', 'PhotoController', ['except' => [
    'create', 'store', 'update', 'destroy'
]]);

Route::resource('photo', 'PhotoController', ['names' => [
    'create' => 'photo.build'
]]);

# 模拟 http 请求方法
@method('PUT')
Verb URI Action Route Name
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

命名 resource 路由的参数

默认情况下,Route::resource 会根据资源名称的「单数」形式创建资源路由的路由参数。你可以在选项数组中传入 parameters 参数来轻松地覆盖每个资源。parameters 数组应该是资源名称和参数名称的关联数组:

Route::resource('user', 'PhotoController', ['parameters' => [
    'photo' => 'photo_in_phone'
]]);
# /user/{photo_in_phone}

重命名所有的动词名 create、edit

# AppServiceProvider
Route::resourceVerbs([
    'create' => 'crear',
    'edit' => 'editar',
]);
  1. 如果想加入其他控制器方法,尽量写在resource控制器路由之前,否则可能会被resource控制器的路由覆盖。
  2. 写的控制器要专一,如果你需要典型的 resource 操作之外的方法,可以考虑将你的控制器分成两个更小的控制器。

参数必须在依赖注入之后传入

# Route::put('user/{id}', 'UserController@update');
public function update(Request $request, $id)
{
    //
}

五、请求

请求将经过TrimStringsConvertEmptyStringsToNull等中间件,这样可以不用担心标准化的问题。如果要禁用此行为,从 App\Http\Kernel 类的 $ middleware 属性中删除它们。

$request->path()
$request->is('admin/*')
$request->url()
$request->fullUrl()
$request->isMethod('post')
$request->all()
$request->input('name')
$request->input('name', 'Sally')     // 第二个参数为默认值
$request->input('products.0.name')
$request->input('products.*.name')
$request->input()
$request->query('name')
$request->query('name', 'Helen')
$request->query()    // 返回所有 query string 的关联数组
$request->name      // laravel 首先查找请求数据,在查找路由参数
$request->input('user.name')     // 获取 json,请求 contentType: "application/json"  
$request->only(['username', 'password'])    // 返回请求关联数组,但不会返回不存在请求数据
$input = $request->only('username', 'password');   // 同上
$input = $request->except(['credit_card']);
$input = $request->except('credit_card');
$request->has('name')
$request->has(['name', 'email'])    // 同时存在
$request->filled('name')    // 存在并且为非空
$request->flash()     // 将当前输入存进 session 中,以便下次请求可以使用它们
$request->flashOnly(['username', 'email'])
$request->flashExcept('password')
return redirect('form')->withInput()    // 等同于:$request->flash(); 
return redirect('form')->withInput(
    $request->except('password')
);
$request->old('username')   // 取出 flash() 内容,并从 session 中清除
   // 不存在返回 null
$request->cookie('name')   // === \Cookie::get('name')
response('Hello World')->cookie(
    'name', 'value', $minutes
);
response('Hello World')->cookie(
    'name', 'value', $minutes, $path, $domain, $secure, $httpOnly
);
Cookie::queue(Cookie::make('name', 'value', $minutes));
Cookie::queue('name', 'value', $minutes);

$cookie = cookie('name', 'value', $minutes);
return response('Hello World')->cookie($cookie);

$request->file('photo')
$request->photo
$request->hasFile('photo')
$request->file('photo')->isValid()
$request->photo->path()
$request->photo->extension()
$path = $request->photo->store('images');
$path = $request->photo->store('images', 's3');
$path = $request->photo->storeAs('images', 'filename.jpg');
$path = $request->photo->storeAs('images', 'filename.jpg', 's3');

配置可信任的代理

六、响应

laravel 会自动将你的 return 数据封装成响应。如果你返回数组,会自动封装成 json。如果返回 Eloquent 集合,也会自动封装成 json。

自定义

return response('Hello World', 200)->header('X-Header-One', 'Header Value')->header('Content-Type', 'text/plain');
// 或者
return response($content)
            ->withHeaders([
                'Content-Type' => $type,
                'X-Header-One' => 'Header Value',
                'X-Header-Two' => 'Header Value',
            ])

# Laravel 内置了一个 cache.headers 中间件,可以用来快速地为路由组设置 Cache-Control 头信息。如果在指令集中声明了 etag,Laravel 会自动将 ETag 标识符设置为响应内容的 MD5 哈希值:

Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function() {
    Route::get('privacy', function () {
        // ...
    });

    Route::get('terms', function () {
        // ...
    });
});

return response($content)
                ->header('Content-Type', $type)
                ->cookie('name', 'value', $minutes);
# ->cookie($name, $value, $minutes, $path, $domain, $secure, $httpOnly)
# Cookie::queue(Cookie::make('name', 'value', $minutes));
# Cookie::queue('name', 'value', $minutes);
# 如果不想某个 cookie 在客户端中加密,
# 请在 App\Http\Middleware\EncryptCookies 的 except 数组中进行配置。

重定向

return redirect('home/dashboard');
return back()->withInput();     // 请确保该路由在`web.php`中
return redirect()->route('login');
return redirect()->route('profile', ['id' => 1]);

return redirect()->route('profile', [$user]);    
// 第二个参数也可以是 $user,路由:profile/{id}。该写法会自动提取 $user 中的 id
# 如果想定制自动提取的字段
public function getRouteKey()
{
    return $this->slug;
} 

return redirect()->action('HomeController@index');

return redirect()->action(
    'UserController@profile', ['id' => 1]
);

return redirect()->away('https://www.google.com');

return redirect('dashboard')->with('status', 'Profile updated!');  
// 设置 cookie,获取 {{ session('status') }}

生成其他类型的响应

return response()
            ->view('hello', $data, 200)
            ->header('Content-Type', $type);

return response()->json([
    'name' => 'Abigail',
    'state' => 'CA'
]);     // json 会自动设置响应头 Content-Type => application/json

实现 json 响应

return response()
            ->json(['name' => 'Abigail', 'state' => 'CA']);
            
return response()
            ->json(['name' => 'Abigail', 'state' => 'CA'])
            ->withCallback($request->input('callback'));

实现下载

return response()->download($pathToFile);
return response()->download($pathToFile, $name, $headers);
return response()->download($pathToFile)->deleteFileAfterSend();

管理文件下载的扩展包 Symfony HttpFoundation,要求下载文件名必须是 ASCII 编码。

流下载

return response()->streamDownload(function () {
    echo GitHub::api('repo')
                ->contents()
                ->readme('laravel', 'laravel')['contents'];
}, 'laravel-readme.md');

展示图片、pdf 等

return response()->file($pathToFile);
return response()->file($pathToFile, $headers);

定制通用响应

// 定制 class ResponseMacroServiceProvider extends ServiceProvider 中的 boot 方法
Response::macro('caps', function ($value) {
    return Response::make(strtoupper($value));
});

// 使用 
return response()->caps('foo');

七、视图

return view('greeting', ['name' => 'James']);
return view('admin.profile', $data);
if (View::exists('emails.customer')) 
return view()->first(['custom.admin', 'admin'], $data);  
return View::first(['custom.admin', 'admin'], $data);   // 同上
return view('greetings', ['name' => 'Victoria']);
return view('greeting')->with('name', 'Victoria');

public function boot()
{
    View::share('key', 'value');  // class AppServiceProvider
    // 在所有视图中都可以使用 {{ $key }}
}

视图 composers

// service provider
public function boot()
{
    View::composer(
        'profile', 'App\Http\ViewComposers\ProfileComposer'
    );            // ProfileComposer@compose 在 profile 视图生成前调用

    View::composer('dashboard', function ($view) {
        //
    });
}

class ProfileComposer
{
    protected $users;

    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    public function compose(View $view)
    {
        $view->with('count', $this->users->count());
    }
}
# 多个视图
View::composer(
    ['profile', 'dashboard'],
    'App\Http\ViewComposers\MyViewComposer'
);
# 所有
View::composer('*', function ($view) {
    //
});

视图 creator

# 视图 creators 和视图合成器非常相似。唯一不同之处在于:视图构造器在视图实例化之后立即执行,而视图合成器在视图即将渲染时执行。
View::creator('profile', 'App\Http\ViewCreators\ProfileCreator');

视图 creator 和视图合成器非常相似。唯一不同之处在于:视图构造器在视图实例化之后立即执行,而视图合成器在视图即将渲染时执行。creator 在 composer 之前。

八、url 生成

echo url("/posts/{$post->id}");
// http://example.com/posts/1

echo url()->current();     // === URL::current();
echo url()->full();        // === URL::full();
echo url()->previous();    // === URL::previous();

echo route('post.show', ['post' => 1]);
// http://example.com/post/1

echo route('post.show', ['post' => $post]);      
// $post 是一个Eloquent model,该写法自动提取出主键

$url = action('HomeController@index');
action([HomeController::class, 'index']);
$url = action('UserController@profile', ['id' => 1]);

URL::defaults(['locale' => $request->user()->locale]);  
// 设置默认值 Route::get('/{locale}/posts', function () { ... })->name('post.index');

签名 URL

# Laravel 允许你轻松地为命名路径创建 「签名」 URL。这些 URL 在查询字符串后附加了 「签名」哈希,允许 Laravel 验证 URL 自创建以来未被修改过。签名 URL 对于可公开访问但需要一层防止 URL 操作的路由特别有用。
return URL::signedRoute('unsubscribe', ['user' => 1]);

return URL::temporarySignedRoute(
    'unsubscribe', now()->addMinutes(30), ['user' => 1]
);

Route::get('/unsubscribe/{user}', function (Request $request) {
    if (! $request->hasValidSignature()) {
        abort(401);
    }
    // ...
})->name('unsubscribe');

# 添加到中间件
protected $routeMiddleware = [
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
];
Route::post('/unsubscribe/{user}', function (Request $request) {
    // ...
})->name('unsubscribe')->middleware('signed');

九、session

驱动设置为 array,是用在测试的时候保证不会持久存储 session。

使用 database 作为驱动

Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->unsignedInteger('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity');
});

php artisan session:table
php artisan migrate

session 操作

$request->session()->get('key');
$request->session()->get('key', 'default');
$request->session()->get('key', function () {
    return 'default';
});

session('key');
session('key', 'default');
session(['key' => 'value']);

// 测试 assertSessionHas

$request->session()->all();
if ($request->session()->has('users'))     // 存在且不为 null
if ($request->session()->exists('users'))     // 可以是 null

$request->session()->put('key', 'value');
session(['key' => 'value']);

$request->session()->push('user.teams', 'developers');
$value = $request->session()->pull('key', 'default');     // 取出并删除

$request->session()->flash('status', 'Task was successful!');    // 短暂存储 session 一次
$request->session()->reflash();     // 保持 session 再存储一次
$request->session()->keep(['username', 'email']);     // 保持特定 session 存储一次

$request->session()->forget('key');
$request->session()->forget(['key1', 'key2']);
$request->session()->flush();

$request->session()->regenerate();    
// 手动重新生成 session id,LoginController 内置自动重新生成 session id

自定义 session 驱动

# SessionHandlerInterface 必须实现此接口

十、验证

基础验证

如果请求验证失败,会返回跳转响应,如果是 ajax ,会返回带有状态码 422 的 json 格式的响应。

$validatedData = $request->validate([
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
]);

$validatedData = $request->validate([
    'title' => ['required', 'unique:posts', 'max:255'],
    'body' => ['required'],
]);

如果希望验证了某个字段失败后,不再验证后面的字段。请在前一个字段验证规则中添加bail

嵌套验证
$request->validate([
    'title' => 'bail|required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);
验证失败

$errors 变量被 Web 中间件组提供的 Illuminate\View\Middleware\ShareErrorsFromSession 中间件绑定到视图中。 当这个中间件被应用后,在你的视图中就可以获取到 $error 变量 , 可以使一直假定 $errors 变量存在并且可以安全地使用。

@if ($errors->any())
    
    @foreach ($errors->all() as $error)
  • {{ $error }}
  • @endforeach
@endif @error('title')
{{ $message }}
@enderror

因为全局都会经过TrimStringsConvertEmptyStringsToNull中间件,所以如果要设置某个字段验证时可以为null,请在验证规则中添加nullable

表单请求验证

命令生成
php artisan make:request StoreBlogPost

验证规则

public function rules()
{
    return [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
    ];
}
public function store(StoreBlogPost $request)
{
    // The incoming request is valid...

    // Retrieve the validated input data...
    $validated = $request->validated();
}

添加 after 钩子

public function withValidator($validator)      // withValidator 在验证之前做一些操作
{
    $validator->after(function ($validator) {
        if ($this->somethingElseIsInvalid()) {
            $validator->errors()->add('field', 'Something is wrong with this field!');
        }
    });
}

授权验证

public function authorize()
{
    $comment = Comment::find($this->route('comment'));

    return $comment && $this->user()->can('update', $comment);
}

定制错误消息

public function messages()
{
    return [
        'title.required' => 'A title is required',
        'body.required'  => 'A message is required',
    ];
}

定制验证属性

# :attribute
public function attributes()
{
    return [
        'email' => 'email address',
    ];
}
手动创建验证
$validator = Validator::make($request->all(), [
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
]);

if ($validator->fails()) {
    return redirect('post/create')
        ->withErrors($validator)       // withErrors 接受 validator 或者 MessageBag 或者 array 作为参数
        ->withInput();
}

自动跳转

Validator::make($request->all(), [
    'title' => 'required|unique:posts|max:255',
    'body' => 'required',
])->validate();

命名错误包

return redirect('register')
            ->withErrors($validator, 'login');

// {{ $errors->login->first('email') }}

after 钩子

$validator = Validator::make(...);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add('field', 'Something is wrong with this field!');
    }
});

if ($validator->fails()) {
    //
}
错误信息
$errors = $validator->errors();
echo $errors->first('email');

foreach ($errors->get('email') as $message) {
    //
}

foreach ($errors->get('attachments.*') as $message) {
    //
}

foreach ($errors->all() as $message) {
    //
}

if ($errors->has('email')) {
    //
}

自定义错误消息

$messages = [
    'required' => 'The :attribute field is required.',
];
$validator = Validator::make($input, $rules, $messages);
$messages = [
    'email.required' => 'We need to know your e-mail address!',
];

在语言文件中指定自定义消息

在大多数情况下,您可能会在语言文件中指定自定义消息,而不是直接将它们传递给 Validator。为此,需要把你的消息放置于 resources/lang/xx/validation.php 语言文件内的 custom 数组中。

'custom' => [
    'email' => [
        'required' => 'We need to know your e-mail address!',
    ],
],

在语言文件中指定自定义属性

如果你希望将验证消息的 :attribute 占位符替换为自定义属性名称,你可以在 resources/lang/xx/validation.php 语言文件的 attributes 数组中指定自定义名称:

'attributes' => [
    'email' => 'email address',
],

在语言文件中指定自定义值

有时可能需要将验证消息的 :value 占位符替换为值的自定义文字。例如,如果 payment_type 的值为 cc,使用以下验证规则,该规则指定需要信用卡号:

$request->validate([
    'credit_card_number' => 'required_if:payment_type,cc'
]);

如果此验证规则失败,则会产生以下错误消息:

    当payment type为cc时,credit card number 不能为空。

您可以通过定义 values 数组,在 validation 语言文件中指定自定义值表示,而不是显示 cc 作为支付类型值:

'values' => [
    'payment_type' => [
        'cc' => '信用卡'
    ],
],

现在,如果验证规则失败,它将产生以下消息:

    当payment type 为信用卡时,credit card number不能为空。

可用的验证规则

Accepted
Active URL
After (Date)
After Or Equal (Date)
Alpha
Alpha Dash
Alpha Numeric
Array
Bail
Before (Date)
Before Or Equal (Date)
Between
Boolean
Confirmed
Date
Date Equals
Date Format
Different
Digits
Digits Between
Dimensions (Image Files)
Distinct
E-Mail
Ends With
Exists (Database)
File
Filled
Greater Than
Greater Than Or Equal
Image (File)
In
In Array
Integer
IP Address
JSON
Less Than
Less Than Or Equal
Max
MIME Types
MIME Type By File Extension
Min
Not In
Not Regex
Nullable
Numeric
Present
Regular Expression
Required
Required If
Required Unless
Required With
Required With All
Required Without
Required Without All
Same
Size
Sometimes
Starts With
String
Timezone
Unique (Database)
URL
UUID

官方详细内容:验证规则。

按条件添加验证规则
Validator::make($data, [
    'email' => 'sometimes|required|email',               // sometimes $data 内含有 email 才验证
]);

复杂的验证规则

$v = Validator::make($data, [
    'email' => 'required|email',
    'games' => 'required|numeric',
]);

$v->sometimes('reason', 'required|max:500', function ($input) {
    return $input->games >= 100;
});

$v->sometimes(['reason', 'cost'], 'required', function ($input) {
    return $input->games >= 100;
});
# 传入 闭包 的 $input 参数是 Illuminate\Support\Fluent 的一个实例,可用来访问你的输入或文件对象。

验证数组

$validator = Validator::make($request->all(), [
    'photos.profile' => 'required|image',
]);

$validator = Validator::make($request->all(), [
    'person.*.email' => 'email|unique:users',
    'person.*.first_name' => 'required_with:person.*.last_name',
]);

'custom' => [
    'person.*.email' => [
        'unique' => 'Each person must have a unique e-mail address',
    ]
],
自定义验证规则
php artisan make:rule Uppercase

class Uppercase implements Rule`
{
    public function passes($attribute, $value)
    {
        return strtoupper($value) === $value;
    }
    
    public function message()
    {
        return 'The :attribute must be uppercase.';
    }
}
public function message()
{
    return trans('validation.uppercase');
}

直接使用

use App\Rules\Uppercase;

$request->validate([
    'name' => ['required', new Uppercase],
]);

闭包

$validator = Validator::make($request->all(), [
    'title' => [
        'required',
        'max:255',
        function ($attribute, $value, $fail) {
            if ($value === 'foo') {
                $fail($attribute.' is invalid.');
            }
        },
    ],
]);

注册再使用

# AppServiceProvider
public function boot()
    {
        Validator::extend('foo', function ($attribute, $value, $parameters, $validator) {
            return $value == 'foo';
        });
    }

Validator::extend('foo', 'FooValidator@validate');

自定义替换占位符
为错误消息定义自定义替换占位符

# "foo" => "Your input was invalid!",
# "accepted" => "The :attribute must be accepted.",
public function boot()
{
    Validator::extend(...);

    Validator::replacer('foo', function ($message, $attribute, $rule, $parameters) {
        return str_replace(...);
    });
}

隐藏验证

默认情况下,如果字段值为 null 或是空字符串,并且未设置规则含有 required,那么验证规则都将不再使用,也就是结果会被通过。
所以如果是必须的字段,那么一定要设置 required。通过下面,使得字段一定是 required 。

$rules = ['name' => 'unique:users,name'];
$input = ['name' => ''];
Validator::make($input, $rules)->passes(); // true
Validator::extendImplicit('foo', function ($attribute, $value, $parameters, $validator) {  
    return $value == 'foo';
});

“隐式”扩展只意味着该属性是必需的。 它是否实际上使缺失或空属性失效取决于您。(TODO:不是太明白,也用不太到,暂时不管)
如果您希望在属性为空时运行规则对象,则应实现 Illuminate\Contracts\Validation\ImplicitRule 接口。该接口用作验证器的“标记接口”; 因此,它不包含您需要实现的任何方法。

十一、错误与日志

当您启动一个新的Laravel项目时,已经为您配置了错误和异常处理。App\Exceptions\Handler 类是记录应用程序触发的所有异常然后呈现给用户的位置。

public function report(Exception $exception)
{
    if ($exception instanceof CustomException) {
        //
    }

    parent::report($exception);
}

protected function context()
{
    return array_merge(parent::context(), [
        'foo' => 'bar',
    ]);
}

public function isValid($value)
{
    try {
        // Validate the value...
    } catch (Exception $e) {
        report($e);

        return false;
    }
}


# dontReport 不报告错误
protected $dontReport = [
    \Illuminate\Auth\AuthenticationException::class,
    \Illuminate\Auth\Access\AuthorizationException::class,
    \Symfony\Component\HttpKernel\Exception\HttpException::class,
    \Illuminate\Database\Eloquent\ModelNotFoundException::class,
    \Illuminate\Validation\ValidationException::class,
];


public function render($request, Exception $exception)
{
    if ($exception instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

    return parent::render($request, $exception);
}

resources/views/errors/404.blade.php

{{ $exception->getMessage() }}

php artisan vendor:publish --tag=laravel-errors

Handler.php中的render方法

public function render($request, Exception $exception)
{
    if ($exception instanceof CustomException) {
        return response()->view('errors.custom', [], 500);
    }

    return parent::render($request, $exception);
}

自定义异常

php artisan make:exception TalkException

简单粗暴的 abort

abort(404);

abort(403, 'Unauthorized action.');     // 

{{ $exception->getMessage() }}

Laravel使用Monolog库,为各种强大的日志处理程序提供支持。

配置文件 config/logging.php

默认情况下,Monolog使用与当前环境匹配的“通道名称”进行实例化,例如生产或本地环境。 要更改此值,请为频道的配置添加名称选项:

'stack' => [
    'driver' => 'stack',
    'name' => 'channel-name',
    'channels' => ['single', 'slack'],
],
名称 描述
stack 一个便于创建『多通道』通道的包装器
single 单个文件或者基于日志通道的路径 (StreamHandler)
daily 一个每天轮换的基于 Monolog 驱动的 RotatingFileHandler
slack 一个基于 Monolog 驱动的 SlackWebhookHandler
papertrail 一个基于 Monolog 驱动的 SyslogUdpHandler
syslog 一个基于 Monolog 驱动的 SyslogHandler
errorlog 一个基于 Monolog 驱动的 ErrorLogHandler
monolog 一个可以使用任何支持 Monolog 处理程序的 Monolog 工厂驱动程序
custom 一个调用指定工厂创建通道的驱动程序

配置 Single 和 Daily 通道
single 和 daily 通道包含三个可选配置项:bubble 、permission 和 locking.

名称 描述 默认值
bubble 消息处理后,指示消息是否推送到其他通道 true
permission 日志文件权限 644
locking 写入之前尝试锁定日志文件 false

stack 驱动允许你在单一日志通道中整合多个通道。让我们通过一个产品级应用的配置实例来看看如果使用日志堆栈:

'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['syslog', 'slack'],
    ],

    'syslog' => [
        'driver' => 'syslog',
        'level' => 'debug',
    ],

    'slack' => [
        'driver' => 'slack',
        'url' => env('LOG_SLACK_WEBHOOK_URL'),
        'username' => 'Laravel Log',
        'emoji' => ':boom:',
        'level' => 'critical',
    ],
],

Log::info(‘User failed to login.’, [‘id’ => $user->id]);

写入指定通道
Log::channel(‘slack’)->info(‘Something happened!’);

Log::stack([‘single’, ‘slack’])->info(‘Something happened!’);

#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################

第五章:前端开发(一、Blade。二、本地化。三、前端脚手架。四、Laravel mix 。)

​```php
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################


## 一、Blade

@yield(‘title’)

@section(‘sidebar’)
This is the master sidebar.
@show

// 继承 @endsection 指令仅定义了一个片段, @show 则在定义的同时 立即 yield 这个片段。
@extends(‘layouts.app’)

@section(‘title’, ‘Page Title’)

@section(‘sidebar’)
@parent

This is appended to the master sidebar.

@endsection

@section(‘content’)

This is my body content.


@endsection

@yield(‘content’, View::make(‘view.name’))

{{ $slot }}

@component(‘alert’)
Whoops! Something went wrong!
@endcomponent

@componentFirst([‘custom.alert’, ‘alert’])
Whoops! Something went wrong!
@endcomponent

{{ $title }}
{{ $slot }}

@component(‘alert’)
@slot(‘title’)
Forbidden
@endslot

You are not allowed to access this resource!

@endcomponent

@component(‘alert’, [‘foo’ => ‘bar’])

@endcomponent


AppServiceProvider boot

Blade::component(‘components.alert’, ‘alert’);

@alert([‘type’ => ‘danger’])
You are not allowed to access this resource!
@endalert

@alert
You are not allowed to access this resource!
@endalert

{!! $name !!}

在元素属性中使用@json要求它用单引号括起来。

默认情况下,Blade(和Laravel e帮助程序)将对HTML实体进行双重编码。 
如果要禁用双重编码,请从AppServiceProvider的引导方法中调用Blade :: withoutDoubleEncoding方法:
public function boot()
{
    Blade::withoutDoubleEncoding();
}

@{{ name }}

@verbatim


Hello, {{ name }}.

@endverbatim

@if (count( r e c o r d s ) = = = 1 ) I h a v e o n e r e c o r d ! @ e l s e i f ( c o u n t ( records) === 1) I have one record! @elseif (count( records)===1)Ihaveonerecord!@elseif(count(records) > 1)
I have multiple records!
@else
I don’t have any records!
@endif

@unless (Auth::check())
You are not signed in.
@endunless

@isset($records)
// $records is defined and is not null…
@endisset

@empty($records)
// $records is “empty”…
@endempty

@auth
// The user is authenticated…
@endauth

@guest
// The user is not authenticated…
@endguest

@auth(‘admin’)
// The user is authenticated…
@endauth

@guest(‘admin’)
// The user is not authenticated…
@endguest

@switch($i)
@case(1)
First case…
@break

@case(2)
    Second case...
    @break

@default
    Default case...

@endswitch

@for ($i = 0; $i < 10; $i++)
The current value is {{ $i }}
@endfor

@foreach ($users as $user)

This is user {{ $user->id }}


@endforeach

@forelse ($users as $user)

  • {{ $user->name }}

  • @empty

    No users


    @endforelse

    @while (true)

    I’m looping forever.


    @endwhile

    @foreach ($users as u s e r ) @ i f ( user) @if ( user)@if(user->type == 1)
    @continue
    @endif

  • {{ $user->name }}
  • @if ($user->number == 5) @break @endif

    @endforeach

    @foreach ($users as u s e r ) @ c o n t i n u e ( user) @continue( user)@continue(user->type == 1)

  • {{ $user->name }}
  • @break($user->number == 5)

    @endforeach

    @foreach ($users as u s e r ) @ i f ( user) @if ( user)@if(loop->first)
    This is the first iteration.
    @endif

    @if ($loop->last)
        This is the last iteration.
    @endif
    
    

    This is user {{ $user->id }}

    @endforeach

    @foreach ($users as u s e r ) @ f o r e a c h ( user) @foreach ( user)@foreach(user->posts as p o s t ) @ i f ( post) @if ( post)@if(loop->parent->first)
    This is first iteration of the parent loop.
    @endif
    @endforeach
    @endforeach

    
    #### section 有内容则生效
    

    @hasSection(‘navigation’)


    @yield(‘navigation’)

    @endif

    
    属性	|描述
    --|--
    $loop->index	|当前迭代的索引(从 0 开始计数)。
    $loop->iteration|	当前循环迭代 (从 1 开始计算)。
    $loop->remaining|	循环中剩余迭代的数量。
    $loop->count|	被迭代的数组元素的总数。
    $loop->first|	是否为循环的第一次迭代。
    $loop->last	|是否为循环的最后一次迭代。
    $loop->even	|是否为循环的偶数次迭代。
    $loop->odd	|是否为循环的奇数次迭代。
    $loop->depth|	当前迭代的嵌套深度级数。
    $loop->parent|	嵌套循环中,父循环的循环变量
    

    {{-- This comment will not be present in the rendered HTML --}}

    @php
    //
    @endphp

    @include(‘shared.errors’)

    @includeWhen($boolean, ‘view.name’, [‘some’ => ‘data’])

    @includeFirst([‘custom.admin’, ‘admin’], [‘some’ => ‘data’])

    resources/views/includes/input.blade.php

    Blade::include('includes.input', 'input'); ``` ``` @each('view.name', $jobs, 'job') @each('view.name', $jobs, 'job', 'view.empty') ``` ``` @push('scripts') @endpush

    @stack(‘scripts’)

    @push(‘scripts’)
    This will be second…
    @endpush

    // Later…

    @prepend(‘scripts’)
    This will be first…
    @endprepend

    #### 拓展 Blade
    
    ```php
    # class AppServiceProvider 中 boot 方法
    Blade::directive('datetime', function ($expression) {
    	return "format('m/d/Y H:i'); ?>";
    });
    

    更新Blade指令的逻辑之后,您需要删除所有缓存的Blade视图(php artisan view:clear)。

    
    @datetime($var)
    // format('m/d/Y H:i'); ?>
    

    自定义模版语法

    use Illuminate\Support\Facades\Blade;
    
    public function boot()
    {
        Blade::if('env', function ($environment) {
            return app()->environment($environment);
        });
    }
    
    @env('local')
        // The application is in the local environment...
    @else
        // The application is not in the local environment...
    @endenv
    

    堆栈

    <!-- extend.blade.php -->
    @push('scripts')
        <script src="/example.js"></script>
    @endpush
    
    /**  输出 start
    
    
         输出 end  **/
    
    
    @stack('scripts')
    @stack('scripts')
    

    服务注入

    @inject('metrics', 'App\Services\MetricsService')
    
    <div>
        Monthly Revenue: {{ $metrics->monthlyRevenue() }}.
    </div>
    

    继承

    
    @yield('title')
    
    @section('sidebar')
    这是 master 的侧边栏。
    @show
    
    
    @extends('layouts.app')
      
    @section('title',' Here is a div ')
    // 等同于
    @section('title') Here is a div @endsection
      
    @section('sidebar')
        @parent
        

    This is appended to the master sidebar.

    @endsection

    slot

    
    <div class="alert alert-danger">
        {{ $slot }}
    div>
      
    
    @component('alert')
        <strong>哇!strong> 出现了一些问题!
    @endcomponent
    
    <!-- alert.blade.php -->
    <div class="alert alert-danger">
        <div class="alert-title">{{ $title }}</div>
        {{ $slot }}
    </div>
      
    <!-- other.blade.php -->  
    @component('alert')
        @slot('title')
            拒绝
        @endslot
        你没有权限访问这个资源!
    @endcomponent
    
    /**  输出 start
    
    拒绝
    你没有权限访问这个资源!
    输出 end **/
    @component('alert', ['foo' => 'bar'])
        ...
    @endcomponent
    # 所有的数据都将以变量的形式传递给组件模版 alert.blade.php
    

    Blade & JavaScript 框架

    @{{ name }}
    # 最终输出 {{ name }} ,供一些 js 框架使用。 
    

    如果是大量的需要这么做,可以使用

    @verbatim
        <div class="container">
            Hello, {{ name }}.
        div>
    @endverbatim
    

    为集合数组渲染视图

    // 子视图使用 key 变量作为当前迭代的键名。
    // 注意:通过 @each 不会从父视图继承变量。 如果子需要这些变量,应该使用 @foreach 和 @include。
    @each('view.name', $jobs, 'job')
    @each('view.name', $jobs, 'job', 'view.empty')
      
    // 自己做的例子
    
    
    {{ $aa }}
    @each('alert', ['aaa','bbb'], 'aa') /********** 输出 *********
    aaa
    bbb
    ************************/

    二、本地化

    1. 实时设置本地化语言:App::setLocale($locale);
    2. config/app.php中的fallback_locale选项是设置备用语言,当第一语言不可用,则使用这个。
    3. if (App::isLocale('en'))确认当前语言。
    4. resource/lang文件夹下可以自定义其他国家的语言,与已存在的文件相对应即可。
    /resources
        /lang
            /en
                messages.php
            /es
                messages.php
    			
    return [
        'welcome' => 'Welcome to our application'
    ];
    

    长语句翻译

    如果有大量东西需要翻译,你会发现如果用短字符串或者单词,容易看蒙了,这时候可以使用长的语句去翻译。例如建立文件resources/lang/es.json

    {
        "I love programming.": "Me encanta programar."
    }
    

    取出翻译

    echo __('messages.welcome');       // 如果不存在,返回 messages.welcome
    echo __('I love programming.');
    
    {{ __('messages.welcome') }}
    @lang('messages.welcome')
    
    echo __('messages.welcome', ['name' => 'dayle']);    // 'welcome' => 'Welcome, :name',
    
    'welcome' => 'Welcome, :NAME', // Welcome, DAYLE
    'goodbye' => 'Goodbye, :Name', // Goodbye, Dayle
    
    'apples' => 'There is one apple|There are many apples',
    
    'apples' => '{0} There are none|[1,19] There are some|[20,*] There are many',
    
    echo trans_choice('messages.apples', 10);          
    
    'minutes_ago' => '{1} :value minute ago|[2,*] :value minutes ago',
    echo trans_choice('time.minutes_ago', 5, ['value' => 5]);
    
    'apples' => '{0} There are none|{1} There is one|[2,*] There are :count',
    

    包语言覆盖

    建立对应文件:resources/lang/vendor/{package}/{locale}
    举个栗子, skyrim/hearthfire覆盖messages.php翻译,你应该建立文件resources/lang/vendor/hearthfire/en/messages.php

    三、前端脚手架

    如果不需要 vue 和 bootstrap:php artisan preset none

    写 css

    在编译 css 之前,请安装前端依赖

    npm install
    

    npm run dev命令会处理webpack.mix.js文件中的指令。编译的 css 文件将放在目录public/css中。

    npm run dev
    

    写 js

    安装依赖

    npm install
    

    编译

    npm run dev
    

    编写 vue 组件

    Vue.component(
        'example-component',
        require('./components/ExampleComponent.vue')
    );
    

    使用组件

    @extends('layouts.app')
    
    @section('content')
        
    @endsection
    

    使用 react

    php artisan preset react
    

    四、Laravel mix

    安装与配置

    安装 node 和 npm。

    node -v
    npm -v
    

    Laravel Mix

    安装 mix

    npm install
    

    运行 mix

    // Run all Mix tasks...
    npm run dev
    
    // Run all Mix tasks and minify output...
    npm run production
    

    监听运行 mix

    npm run watch
    

    如果无效请尝试以下命令:

    npm run watch-poll
    

    与样式一起编译

    Less

    less 方法编译 less 到 css。

    mix.less('resources/assets/less/app.less', 'public/css');
    
    mix.less('resources/assets/less/app.less', 'public/css')
       .less('resources/assets/less/admin.less', 'public/css');
    

    指定文件名

    mix.less('resources/assets/less/app.less', 'public/stylesheets/styles.css');
    

    设置 less 选项

    mix.less('resources/assets/less/app.less', 'public/css', {
        strictMath: true
    });
    

    Sass

    mix.sass('resources/assets/sass/app.scss', 'public/css');
    
    mix.sass('resources/assets/sass/app.sass', 'public/css')
       .sass('resources/assets/sass/admin.sass', 'public/css/admin');
    
    mix.sass('resources/assets/sass/app.sass', 'public/css', {
        precision: 5
    });
    

    Stylus

    mix.stylus('resources/assets/stylus/app.styl', 'public/css');
    

    安装插件

    # npm install rupture
    
    mix.stylus('resources/assets/stylus/app.styl', 'public/css', {
        use: [
            require('rupture')()
        ]
    });
    

    PostCSS

    mix.sass('resources/assets/sass/app.scss', 'public/css')
       .options({
            postCss: [
                require('postcss-css-variables')()
            ]
       });
    

    原生 CSS

    mix.styles([
        'public/css/vendor/normalize.css',
        'public/css/vendor/videojs.css'
    ], 'public/css/all.css');
    

    URL 处理

    .example {
        background: url('../images/example.png');
    }
    

    默认情况下, Laravel Mix 和 Webpack 会找到 example.png, 拷贝到 public/images,然后改写url(),变成这样子:

    .example {
      background: url(/images/example.png?d41d8cd98f00b204e9800998ecf8427e);
    }
    

    url('/images/thing.png') 或者 url('http://example.com/images/thing.png') 不会变.

    如果你的目录结构不是默认那样,可以禁用重写url()

    mix.sass('resources/assets/app/app.scss', 'public/css')
       .options({
          processCssUrls: false
       });
    

    禁用后,原来是啥样,现在还是啥样

    .example {
        background: url("../images/thing.png");
    }
    

    Source Maps

    启用 source maps

    mix.js('resources/assets/js/app.js', 'public/js')
       .sourceMaps();
    

    与 js 一起编译

    mix.js('resources/assets/js/app.js', 'public/js');
    

    上面一句代码就支持了以下功能

    1. ES 2015 语法
    2. 模块
    3. 编译 .vue 文件
    4. 生产环境压缩代码

    依赖提取
    如果将依赖绑定你的 js。这样你更新一点点代码就要使得客户端重新下载你的依赖。这时候可以使用依赖提取方法extract

    mix.js('resources/assets/js/app.js', 'public/js')
       .extract(['vue'])
    

    运行后生成以下文件

    • public/js/manifest.jsThe Webpack 运行时的 manifest 文件
    • public/js/vendor.js你的依赖
    • public/js/app.js你的 js

    引用顺序不能出错。

    React

    Mix 可以自动安装 Babel 插件来支持 React。为了使用 React,你只需将 mix.js() 的调用替换成 mix.react() 即可:

    mix.react('resources/assets/js/app.jsx', 'public/js');
    

    原生 JS

    类似使用 mix.styles() 来合并多个样式表一样,你也可以使用 scripts() 方法来合并并压缩多个 JavaScript 文件:

    mix.scripts([
        'public/js/admin.js',
        'public/js/dashboard.js'
    ], 'public/js/all.js');
    

    mix.babel()mix.scripts()有点不同,那就是mix.babel()会将所有 ES2015 的代码转换为所有浏览器都能识别的原生 JavaScript。

    自定义 webpack 配置

    两种方式

    合并定制配置

    mix.webpackConfig({
        resolve: {
            modules: [
                path.resolve(__dirname, 'vendor/laravel/spark/resources/assets/js')
            ]
        }
    });
    

    定制配置文件

    复制node_modules/laravel-mix/setup/webpack.config.js到你的根目录。然后修改package.json文件中的--config参数。采用这种方法进行自定义,如果后续有更新时,需要手动合并到你的自定义文件中。

    拷贝文件和目录

    mix.copy('node_modules/foo/bar.css', 'public/css/bar.css');
    

    拷贝目录使用 copyDirectory 防止扁平化目录结构(目录分别都是从属关系)

    mix.copyDirectory('assets/img', 'public/img');
    

    版本控制以清除缓存

    mix.js('resources/assets/js/app.js', 'public/js')
       .version();
    

    获取文件名

    
    

    指定版本控制只适用于生产环境

    npm run production:

    mix.js('resources/assets/js/app.js', 'public/js');
    
    if (mix.inProduction()) {
        mix.version();
    }
    

    Browsersync 重新加载

    BrowserSync 可以自动监控你的文件变化,并将更改注入浏览器,而无需手动刷新。你可以通过调用 mix.browserSync() 方法来启用这个功能的支持:

    mix.browserSync('my-domain.test');
    
    // Or...
    
    // https://browsersync.io/docs/options
    mix.browserSync({
        proxy: 'my-domain.test'
    });
    

    你可以将字符串 (代理) 或者对象 (BrowserSync 设置) 传给这个方法。再使用 npm run watch 命令来开启 Webpack 的开发服务器。现在,当你修改脚本或者 PHP 文件时,浏览器会即时刷新页面以响应你的更改。

    环境变量

    .env文件中设置

    MIX_SENTRY_DSN_PUBLIC=http://example.com
    

    如果 npm run watch 下更改了值,需要重新 npm run watch

    process.env.MIX_SENTRY_DSN_PUBLIC
    

    通知

    默认会进行系统通知编译情况,如果不希望在生产服务器上通知:

    mix.disableNotifications();
    

你可能感兴趣的:(php,Laravel,6,Laravel,6,laravel6)