再读 Laravel 5.5 文档

本文档详细解读 Laravel 5.5 的各项特性和功能,涵盖版本控制、新特性、贡献指南、安装配置、架构设计、基础操作、中间件、认证授权、数据库管理、Eloquent ORM、测试等多个方面,旨在帮助开发者深入理解和应用 Laravel 5.5。
摘要由CSDN通过智能技术生成

本文档前言

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

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

第一章:前言(一、版本控制方案。二、Laravel 5.5 新特性。三、贡献指南。四、api 文档。)

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

一、版本控制方案

  1. 安装版本包时应当指定主版本号和次版本号,如:5.5.*
  2. Laravel 5.5 是 LTS 版本,提供两年的 bug 修复和三年的安全修复。非 LTS 版本,只提供六个月的 bug 修复和一年的安全修复。

二、Laravel 5.5 的新特性(简单介绍)

Horizon ( redis 队列配置仪表盘)、包自动发现、api 资源、控制台命令自动注册、队列任务链、队列任务速率控制、基于时间的任务尝试、自定义的验证规则、可信任的代理集成、按需通知、可渲染的邮件、自定义可报告可渲染的异常、请求对象中的认证方法、一致的异常处理、缓存锁、读写数据库配置 sticky 选项。

三、贡献指南

本节主要指示用户在提出增加框架新特性或者提交框架使用反馈时,需要做些什么、怎么做。

四、api 文档

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


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

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

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

一、安装

两种安装方式
  1. composer global require "laravel/installer"
    laravel new blog // 无法指定版本
  2. composer create-project --prefer-dist laravel/laravel blog "5.5.*"
安装须知
  1. 根目录是public
  2. 必须设置 web 服务器可读写storagebootstrap/cache目录及其子目录
  3. 在 web 服务器配置中设置优雅链接 (即去除index.php

二、配置

环境配置
  1. .env 文件内的变量会被系统级别或服务器级别的变量覆盖。
  2. .env 文件内的变量通过env()函数获取,config目录下的变量通过config()函数获取。
  3. 在运行PHPUnit测试时或执行以--env=testing为选项Artisan命令时,.env.testing会覆盖 .env 文件中的值。
操作配置、缓存配置
$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

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

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

四、目录结构

根目录下的各个目录
  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 目录下的很多目录是命令生成的。查看生成命令:php artisan make:list

  1. Console:包含自定义的命令和用来注册命令、定义计划任务的内核文件。
  2. Events:事件目录。
  3. Exceptions:异常和异常处理目录。
  4. Http:包含了控制器、中间件和表单请求。几乎所有请求的处理逻辑都被放在这里。
  5. Jobs:储存队列任务。
  6. Listeners:存储事件的监听。
  7. Mail:存储邮件类目录。
  8. Notifications:存放通知类。laravel 内置了很多驱动: email, Slack, SMS, database。
  9. Policies:授权策略类目录。
  10. Providers:包含了所有服务提供者。服务提供者通过在服务容器上绑定服务、注册事件或者执行其他任务来启动你的应用。
  11. Rules:储存自定义验证规则对象。
routes目录下的各个目录
  1. web.php内的路由将应用 web 中间件组(功能:session 状态,CSRF 保护,cookie 加密等)。
  2. api.php内的路由将应用 api 中间件组(功能:访问速率控制等)。所有的请求将通过令牌认证,无 session 状态。
  3. consoles.php定义了所有基于控制台命令的闭包。
  4. channels.php注册了所有的事件广播频道。

五、Homestead

本节介绍了 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

  3. 克隆仓库 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

六、部署

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

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_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

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

部署优化

composer install --optimize-autoloader
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方法,返回值就是响应内容。

二、服务容器

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

  1. 如果不依赖任何接口,则不需要将类绑定到容器中。
  2. 无需指示容器如何构建这些对象,因为它可以使用反射自动解析。
绑定

基础绑定

$this->app->bind('HelpSpot\API', function ($app) {
   
    return new HelpSpot\API($app->make('HttpClient'));
});  
// Note that we receive the container itself as an argument to the resolver. We can then use the container to resolve sub-dependencies of the object we are building.
// 然而我并不知道上面说的什么鸡脖子东西。

绑定单例

$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 DecoratedService($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 接口,所以就可以用该接口的类型提示解析
# get 方法通过 id 解析,比较局限,建议别用
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将会被路由器分发到路由或控制器,同时运行路由指定的中间件。

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

如果只是绑定服务到服务容器,可以选择将该服务提供者设置为延迟

protected $defer = true;
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;
...
# 注入 Publisher $publisher
$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\Writer 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\Logging\Log Log
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参数。

两个 api RouterRoute
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::redirect('/here', '/there', 301);

# 只需要返回一个视图
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');
// 在中间件中检查当前路由
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"...
    });
});
表单伪造
<input type="hidden" name="_method" value="PUT">
// 或者 {
   { method_field('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);
    });
}

二、中间件

php artisan make:middleware TestMiddleware

# 中间件操作发生请求被处理之前
public function handle($request, Closure $next)
{
   
    // 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的路由中。

给 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 是同一实例,请将这个中间件单例绑定到服务容器中。
public function handle($request, Closure $next)
{
   
    return $next($request);
}

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

三、CSRF 保护

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

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

指定 uri 不去应用 csrf 保护
1. 不将路由写入route/web.php
2. 在VerifyCsrfToken中间件的排除数组中添加你的 uri

protected $except = [
    'stripe/*',
    'http://example.com/foo/bar',
    'http://example.com/foo/*',
];

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

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

四、控制器

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

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

# Route::get('foo', 'Photos\AdminController@method');
public function __invoke($id)
{
   
     return view('user.profile', ['user' => User::findOrFail($id)]);
}

控制器的构造函数中的中间件的玩法

$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 Route::resource('photos', 'PhotoController'); Route::apiResource('photo', 'PhotoController'); // 没有 create 和 edit 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_field('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等中间件,这样可以不用担心标准化的问题。

$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->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') redirect('form')->withInput() // 等同于:$request->flash(); redirect('form'); redirect('form')->withInput( $request->except('password') ); $request->old('username') // 取出 flash() 内容,并从 session 中清除 <input type="text" name="username" value="{
   { old('username') }}"> // 不存在返回 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); 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('Content-Type', 'text/plain'); // 或者 return response($content) ->withHeaders([ 'Content-Type' => $type, 'X-Header-One' => 'Header Value', 'X-Header-Two' => 'Header Value', ]) 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(
    '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

实现 jsonp 响应

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(true);

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

展示图片、pdf 等

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

定制通用响应

// 定制
Response::macro('caps', function ($value) {
   
    return Response::make(strtoupper($value));
});

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

七、视图

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

public function boot()
{
    View::share('key', 'value');
}

视图 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

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'); $url = action('UserController@profile', ['id' => 1]); URL::defaults(['locale' => $request->user()->locale]); // 设置默认值 Route::get('/{locale}/posts', function () { ... })->name('post.index');

九、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']); $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()->flush(); $request->session()->regenerate(); // 手动重新生成 session id,LoginController 内置自动重新生成 session id

自定义 session 驱动

# SessionHandlerInterface 必须实现此接口
<?php
namespace App\Extensions;    // 随意放置于哪个目录

class MongoSessionHandler implements \SessionHandlerInterface
{
   
    public function open($savePath, $sessionName) {
   }
    public function close() {
   }
    public function read(
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值