Laravel 学习交流 QQ 群:375462817
本记录文档前言
Laravel 文档写的很好,只是新手看起来会有点吃力,需要结合经验和网上的文章,多读、细读才能更好的理解。多读、细读官方文档!!!本文类似于一个大纲,欲知其中详情,且去细读官方文档:Laravel 6.0 docs。
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
Laravel 6.0 是 LTS 版本,生产日期,2019 年 09 月。提供两年的 bug 修复和三年的安全修复。非 LTS 版本,只提供六个月的 bug 修复和一年的安全修复。
https://laravel.com/docs/6.0/upgrade
https://laravel.com/docs/6.0/contributions
官方 api 文档地址:https://laravel.com/api/6.0/
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
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
composer global require "laravel/installer"
laravel new blog
// 无法指定版本composer create-project --prefer-dist laravel/laravel blog "6.0.*"
public
storage
和bootstrap/cache
目录及其子目录index.php
)php artisan key:generate
mv .env.example .env
# 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
.env
文件内的变量会被系统级别或服务器级别的变量覆盖。APP_NAME="My Application"
.env
文件内的变量通过env()
函数获取,config
目录下的变量通过config()
函数获取。PHPUnit
测试时或执行以--env=testing
为选项Artisan
命令时,.env.testing
会覆盖 .env
文件中的值。$environment = App::environment();
if (App::environment('local')) {
// 环境是 local
}
if (App::environment(['local', 'staging'])) {
// 环境是 local 或 staging...
}
# 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
当应用程序处于维护模式时,不会处理队列任务。退出维护模式后会继续处理。
app
:应用程序核心目录,几乎项目所有的类都在这里。bootstrap
:包含框架启动文件 app.php
,和启动时为了优化性能而生成的文件。config
:包含所有配置文件。最好是读一遍这些文件,了解你可以轻松配置哪些内容。database
:包含数据库填充、迁移、模型工厂文件。可以用作 SQLite
数据库存放目录。public
:静态资源目录,并包含了首页文件 index.php
。resource
:包含了未编译的源文件(模板、语言、资源)。routes
:包含了所有的路由定义。storage
:包含了编译好的模板文件,session 文件,缓存文件,日志等文件。tests
:包含了自动测试文件。运行测试命令 php vendor/bin/phpunit
。vendor
:composer
依赖目录。app
目录下的各个目录app 目录下的很多目录是命令生成的。由 Composer 使用 PSR-4 自动加载标准自动加载。
查看生成命令:php artisan make:list
。
Broadcasting
:包含所有 broadcast channel 类。Console
:包含自定义的命令和用来注册命令、定义计划任务的内核文件。Events
:事件目录。Exceptions
:异常和异常处理目录。Http
:包含了控制器、中间件和表单请求。几乎所有请求的处理逻辑都被放在这里。Jobs
:储存队列任务。Listeners
:存储事件的监听。Mail
:存储邮件类目录。Notifications
:存放通知类。laravel 内置了很多驱动: email, Slack, SMS, database。Policies
:授权策略类目录。Providers
:包含了所有服务提供者。服务提供者通过在服务容器上绑定服务、注册事件或者执行其他任务来启动你的应用。Rules
:储存自定义验证规则对象。routes
目录下的各个目录web.php
内的路由将应用 web
中间件组(功能:session 状态,CSRF 保护,cookie 加密等)。api.php
内的路由将应用 api
中间件组(功能:访问速率控制等)。所有的请求将通过 token 认证,无 session 状态。consoles.php
定义了所有基于控制台命令的闭包。channels.php
注册了所有的事件广播频道。本节介绍了 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。
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
git clone https://github.com/laravel/homestead.git ~/Homestead
cd ~/Homestead
git checkout v7.1.2
(切换到稳定版本)bash init.sh
或者init.bat
(生成 Homestead.yaml
文件)可以是 virtualbox, vmware_fusion, vmware_workstation, parallels 或者 hyperv
provider: virtualbox
folders
属性用于配置本地和homestead
环境的同步文件夹。
由于多站点或项目有太多文件而导致性能变差时候,可以设置不同的文件夹来同步不同的项目。
NFS
也可以用来优化性能,windows 不支持。使用NFS
时,安装vagrant-bindfs
插件可维护正确的文件和文件夹权限。
利用 options
键可以设置其他选项。
homestead
使用sites
属性让你轻松配置nginx
域名。使用vagrant reload --provision
更新虚拟机中的nginx
配置。
配置好域名,记得在本地 hosts 文件添加解析记录。例如:192.168.10.10 homestead.test
。
# 修改 homestead.rb
# settings[“version”] ||= “>= 0”
# 找到 composer self-update 删除它
vagrant up
vagrant destroy --force
这样你就可以在其他项目目录内,通过 vagrant up 来进行该项目的工作。
composer require laravel/homestead --dev
// make 命令自动生成 Vagrantfile 和 Homestead.yaml
php vendor/bin/homestead make // linux & mac
vendor\\bin\\homestead make // windows
box: laravel/homestead
ip: "192.168.10.10"
memory: 2048
cpus: 4
provider: virtualbox
mariadb: true
需要指定版本,默认安装将创建一个名为 ‘homestead’ 的集群,确保你的虚拟机内存是elasticsearch
的两倍。
box: laravel/homestead
ip: "192.168.10.10"
memory: 4096
cpus: 4
provider: virtualbox
elasticsearch: 6
bash
命令别名alias ..='cd ..' // 随后记得 vagrant reload --provision
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 up
,homestead ssh
mysql 127.0.0.1 33060
postgreSQL 127.0.0.1 54320
在虚拟机里面连接数据库的端口还是默认值 3306
和 5432
。
账号密码: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
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 用来捕获你的邮件,并检查它。你的邮件并不会真的发出去。
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 命令,就会在互联网中暴露的你虚拟机。
只兼容 nginx
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)"
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
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
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
本节主要概括了框架运行的生命周期。
public/index.php
。composer
自动加载文件,然后从 bootstrap/app.php
实例化一个服务容器(服务容器,顾名思义是一个容器,可以比作是一个药箱,药箱当然要放药了,药就是所谓的服务提供者。在启动时候当然不能把框架里的所有的药都加载进来,只能加载基础的药。所以这一步还加载了一些基础的服务提供者。)。app/Http/Kernel.php
或者 app/Console/Kernel.php
。app/Http/Kernel.php
扩展了Illuminate\Foundation\Http\Kernel
类,父类强制在处理请求前应该做哪些操作,操作内容都放到了 bootstrappers
数组里面(配置错误处理、配置记录日志、检测应用环境、注册和启动服务提供者等)。子类在数组middleware
中规定了请求在被处理前必须经过的一些处理(读写session
、判断是否处于维护模式、验证 csrf 令牌等)。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
分发到路由或控制器,同时运行路由指定的中间件。
php artisan make:provider RiakServiceProvider
register
和 boot
。register
只负责绑定一些东西到容器。boot
可以使用类型提示解析等来完成任意你想做的事情,这些都归功于容器调用所有服务提供者的register
方法之后才去调用boot
方法。config/app.php
的providers
数组中注册服务提供者。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
就是各种别名。当你使用某个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');
}
原生用法 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);
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 |
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 |
Facades
和 Contracts
没有什么值得注意的区别,但是当你开发第三方包的时候,最好使用 Contracts
,这样有利于你编写测试,否则如果使用 Facades
,因为是第三方包,将不能访问 facade
测试函数。
在构造函数中类型提示注入就行了。
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() |
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
web/api.php
中定义的路由会自动添加api
前缀,如需修改该前缀,可以在RouteServiceProvider
修改。
在 web.php
路由里的 POST, PUT, DELETE
方法,在提交表单时候必须加上CSRF
参数。
* 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);
}
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
文件中的路由或全局中间件列表中。
VerifyCsrfToken
中间件存在于web
中间件组中,它实现了 CSRF 保护。而该中间件组被默认应用在web.php
文件中的所有路由。
默认情况下,resources/assets/js/bootstrap.js
文件会使用Axios HTTP
库注册csrf-token
meta 标签的值。如果不使用这个库,需要自己去设置。
指定 uri 不去应用 csrf 保护
route/web.php
。VerifyCsrfToken
中间件的排除数组 $except 中添加你的 uriprotected $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
请求头的值。Angular
、Axios
等会自动执行上述操作。
默认情况下,resources/js/bootstrap.js
文件包含 Axios HTTP
库,它会自动为您发送此文件。
定义控制器可以不去继承Controllers
,这样你将不能使用middleware
、validate
、dispatch
等方法。
如果你的控制器只有一个方法,可以这么玩儿:
# 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',
]);
- 如果想加入其他控制器方法,尽量写在
resource
控制器路由之前,否则可能会被resource
控制器的路由覆盖。- 写的控制器要专一,如果你需要典型的
resource
操作之外的方法,可以考虑将你的控制器分成两个更小的控制器。
参数必须在依赖注入之后传入
# Route::put('user/{id}', 'UserController@update');
public function update(Request $request, $id)
{
//
}
请求将经过TrimStrings
、ConvertEmptyStringsToNull
等中间件,这样可以不用担心标准化的问题。如果要禁用此行为,从 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 之前。
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');
驱动设置为 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
因为全局都会经过
TrimStrings
和ConvertEmptyStringsToNull
中间件,所以如果要设置某个字段验证时可以为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
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!’);
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
#########################################################################################
```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.
@yield(‘content’, View::make(‘view.name’))
@component(‘alert’)
Whoops! Something went wrong!
@endcomponent
@componentFirst([‘custom.alert’, ‘alert’])
Whoops! Something went wrong!
@endcomponent
{{ $slot }}
@component(‘alert’)
@slot(‘title’)
Forbidden
@endslot
You are not allowed to access this resource!
@endcomponent
@component(‘alert’, [‘foo’ => ‘bar’])
…
@endcomponent
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 !!}
默认情况下,Blade(和Laravel e帮助程序)将对HTML实体进行双重编码。
如果要禁用双重编码,请从AppServiceProvider的引导方法中调用Blade :: withoutDoubleEncoding方法:
public function boot()
{
Blade::withoutDoubleEncoding();
}
@{{ name }}
@verbatim
@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 }}
@forelse ($users as $user)
No users
@while (true)
I’m looping forever.
@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’)
@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’])
@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
<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
@{{ 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
************************/
App::setLocale($locale);
config/app.php
中的fallback_locale
选项是设置备用语言,当第一语言不可用,则使用这个。if (App::isLocale('en'))
确认当前语言。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
安装 node 和 npm。
node -v
npm -v
Laravel Mix
安装 mix
npm install
// 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();
mix.js('resources/assets/js/app.js', 'public/js');
上面一句代码就支持了以下功能
依赖提取
如果将依赖绑定你的 js。这样你更新一点点代码就要使得客户端重新下载你的依赖。这时候可以使用依赖提取方法extract
。
mix.js('resources/assets/js/app.js', 'public/js')
.extract(['vue'])
运行后生成以下文件
public/js/manifest.js
: The 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。
两种方式
合并定制配置
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 可以自动监控你的文件变化,并将更改注入浏览器,而无需手动刷新。你可以通过调用 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();