标签 "PHP" 下的文章

Blade 模板引擎

模板继承

定义布局:

<!-- 存放在 resources/views/layouts/app.blade.php -->
<html>
    <head>
        <title>App Name - @yield('title')</title>
    </head>
    <body>
        @section('sidebar')
            This is the master sidebar.
        @show
        <div class="container">
            @yield('content')
        </div>
    </body>
</html>

继承布局:

<!-- 存放在 resources/views/child.blade.php -->
@extends('layouts.app')

@section('title', 'Page Title')

@section('sidebar')
    @parent
    <p>This is appended to the master sidebar.</p>
@endsection

@section('content')
    <p>This is my body content.</p>
@endsection

数据显示

注:Blade 的 {{}} 语句已经经过 PHP 的 htmlentities 函数处理以避免 XSS 攻击。

Hello, {{ $name }}.

The current UNIX timestamp is {{ time() }}.

输出存在的数据, 两种方式都可以:

{{ isset($name) ? $name : 'Default' }}

{{ $name or 'Default' }}

显示原生数据:

Hello, {!! $name !!}.

流程控制

if 语句:

@if (count($records) === 1)
    I have one record!
@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

循环:

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

@foreach ($users as $user)
    <p>This is user {{ $user->id }}</p>
@endforeach

@forelse ($users as $user)
    <li>{{ $user->name }}</li>
@empty
    <p>No users</p>
@endforelse

@while (true)
    <p>I'm looping forever.</p>
@endwhile

使用循环的时候还可以结束循环或跳出当前迭代:

@foreach ($users as $user)
    @if ($user->type == 1)
        @continue
    @endif

    <li>{{ $user->name }}</li>

    @if ($user->number == 5)
        @break
    @endif
@endforeach

还可以使用指令声明来引入条件:

@foreach ($users as $user)
    @continue($user->type == 1)

        <li>{{ $user->name }}</li>

    @break($user->number == 5)
@endforeach

$loop 变量

在循环的时候, 可以在循环体中使用 $loop 变量, 该变量提供了一些有用的信息, 比如当前循环索引, 以及当前循环是不是第一个或最后一个迭代:

@foreach ($users as $user)
    @if ($loop->first)
        This is the first iteration.
    @endif

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

    <p>This is user {{ $user->id }}</p>
@endforeach

如果你身处嵌套循环, 可以通过 $loop 变量的 parent 属性访问父级循环:

@foreach ($users as $user)
    @foreach ($user->posts as $post)
        @if ($loop->parent->first)
            This is first iteration of the parent loop.
        @endif
    @endforeach
@endforeach

$loop 变量还提供了其他一些有用的属性:

属性描述
$loop->index当前循环迭代索引 (从0开始)
$loop->iteration当前循环迭代 (从1开始)
$loop->remaining当前循环剩余的迭代
$loop->count迭代数组元素的总数量
$loop->first是否是当前循环的第一个迭代
$loop->last是否是当前循环的最后一个迭代
$loop->depth当前循环的嵌套层级
$loop->parent嵌套循环中的父级循环变量

模板注释

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

嵌入 PHP 代码

@php
    //
@endphp

基本路由

// 接收一个 URI 和一个闭包
Route::get('hello', function () {
    return 'Hello, Laravel';
});

// 支持的路由方法
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::match(['get', 'post'], '/', function () {
    //
});

// 注册所有路由方法
Route::any('foo', function () {
    //
});

路由参数

  • 使用花括号包裹
  • 路由参数不能包含 - 字符, 需要的话可以使用 _ 替代
// 捕获用户 ID
Route::get('user/{id}', function ($id) {
    return 'User '.$id;
});

// 捕获多个参数
Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) {
    //
});

// 可选参数
Route::get('user/{name?}', function ($name = null) {
    return $name;
});
Route::get('user/{name?}', function ($name = 'John') {
    return $name;
});

// 正则约束
Route::get('user/{name}', function ($name) {
    //
})->where('name', '[A-Za-z]+');

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]+']);

命名路由

// 为路由闭包指定名称
Route::get('user/profile', function () {
    //
})->name('profile');

// 为控制器操作指定名称
Route::get('user/profile', 'UserController@showProfile')->name('profile');

// 使用命名路由生成 URL: 不带参数
$url = route('profile');
return redirect()->route('profile');

// 使用命名路由生成 URL: 附带参数
Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');
$url = route('profile', ['id' => 1]);

路由群组

中间件

Route::group(['middleware' => 'auth'], function () {
    Route::get('/', function () {
        // 使用 Auth 中间件
    });
    Route::get('user/profile', function () {
        // 使用 Auth 中间件
    });
});

命名空间

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

子域名路由

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

路由前缀

Route::group(['prefix' => 'admin'], function () {
    Route::get('users', function () {
        // 匹配 "/admin/users" URL
    });
});

表单方法伪造

<form action="/foo/bar" method="POST">
    <input type="hidden" name="_method" value="PUT">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>

或使用辅助函数 method_field :

{{ method_field('PUT') }}

访问当前路由

$route  = Route::current();
$name   = Route::currentRouteName();
$action = Route::currentRouteAction();

路由缓存

# 添加路由缓存
php artisan route:cache
# 移除路由缓存
php artisan route:clear

路由模型绑定

隐式绑定

// {user} 与 $user 绑定, 如果数据库中找不到对应的模型实例, 会自动生成 HTTP 404 响应
Route::get('api/users/{user}', function (App\User $user) {
    return $user->email;
});

// 自定义键名: 重写模型 getRouteKeyName 方法
/**
 * Get the route key for the model.
 *
 * @return string
 */
public function getRouteKeyName()
{
    return 'slug';
}

显式绑定

要注册显式绑定, 需要使用路由的 model 方法来为给定参数指定绑定类. 应该在 RouteServiceProvider 类的 boot 方法中定义模型绑定:

public function boot()
{
    parent::boot();
    Route::model('user', App\User::class);
}

定义一个包含 {user} 参数的路由:

$router->get('profile/{user}', function(App\User $user) {
    //
});

如果请求 URLprofile/1, 就会注入一个用户 ID1User 实例, 如果匹配的模型实例在数据库不存在, 会自动生成并返回 HTTP 404 响应.

自定义解析逻辑

如果你想要使用自定义的解析逻辑, 需要使用 Route::bind 方法, 传递到 bind 方法的闭包会获取到 URI 请求参数中的值, 并且返回你想要在该路由中注入的类实例:

public function boot()
{
    parent::boot();
    Route::bind('user', function($value) {
        return App\User::where('name', $value)->first();
    });
}

快速入门

更换表名

protected $table = 'my_flights';

更换主键名称

protected $primaryKey  = 'id';

注意: Eloquent 默认主键字段是自增的整型数据, 这意味着主键将会被自动转化为 int 类型, 如果你想要使用非自增或非数字类型主键, 必须在对应模型中设置 $incrementing 属性为 false , 如果主键不是整型, 还要设置 $keyType 属性值为 string.

关闭时间戳记录

public $timestamps = false;

获取模型数据

// Eloquent 的 all 方法返回模型表的所有结果
$flights = App\Flight::all();

foreach ($flights as $flight) {
    echo $flight->name;
}

// 添加约束条件
$flights = App\Flight::where('active', 1)
    ->orderBy('name', 'desc')
    ->take(10)
    ->get();

获取单个模型

// 通过主键获取模型
$flight = App\Flight::find(1);
// 获取匹配查询条件的第一个模型
$flight = App\Flight::where('active', 1)->first();
// 通过传递主键数组来调用 find 方法, 这将会返回匹配记录集合
$flights = App\Flight::find([1, 2, 3]);

获取聚合结果

$count = App\Flight::where('active', 1)->count();
$max = App\Flight::where('active', 1)->max('price');

插入记录

$flight = new Flight;
$flight->name = $request->name;
$flight->save();

更新模型

$flight = App\Flight::find(1);
$flight->name = 'New Flight Name';
$flight->save();

批量更新

App\Flight::where('active', 1)
    ->where('destination', 'San Diego')
    ->update(['delayed' => 1]);

删除模型

// 删除
$flight = App\Flight::find(1);
$flight->delete();

// 通过主键删除模型
App\Flight::destroy(1);
App\Flight::destroy([1, 2, 3]);
App\Flight::destroy(1, 2, 3);

// 通过查询删除模型
$deletedRows = App\Flight::where('active', 0)->delete();

软删除

// Eloquent 模型
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model
{
    use SoftDeletes;
    /**
     * 应该被调整为日期的属性
     *
     * @var array
     */
    protected $dates = ['deleted_at'];
}

// 数据表结构添加 deleted_at 列
Schema::table('flights', function ($table) {
    $table->softDeletes();
});

// 判断给定模型实例是否被软删除, 可以使用 trashed 方法
if ($flight->trashed()) {
    // ...
}

// 查询被软删除的模型
$flights = App\Flight::withTrashed()
    ->where('account_id', 1)
    ->get();
$flight->history()->withTrashed()->get();

// 只获取软删除模型
$flights = App\Flight::onlyTrashed()
    ->where('airline_id', 1)
    ->get();

// 恢复软删除模型
$flight->restore();

// 使用 restore 方法来快速恢复多个模型, 不会触发任何模型事件
App\Flight::withTrashed()
    ->where('airline_id', 1)
    ->restore();
$flight->history()->restore();

本地作用域

/**
 * 只包含活跃用户的查询作用域
 *
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function scopePopular($query)
{
    return $query->where('votes', '>', 100);
}

/**
 * 只包含激活用户的查询作用域
 *
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function scopeActive($query)
{
    return $query->where('active', 1);
}
// 使用本地作用域
$users = App\User::popular()->active()->orderBy('created_at')->get();

动态作用域

/**
 * 让查询只包含给定类型的用户
 *
 * @param \Illuminate\Database\Eloquent\Builder $query
 * @param mixed $type
 * @return \Illuminate\Database\Eloquent\Builder
 */
public function scopeOfType($query, $type)
{
    return $query->where('type', $type);
}
// 使用动态作用域
$users = App\User::ofType('admin')->get();

模型关联

一对一关联

// 拥有
class User extends Model
{
    /**
     * 获取关联到用户的手机
     */
    public function phone()
    {
        // Phone : 关联的模型
        // Phone : user_id 外键
        // User  : id      主键
        return $this->hasOne('App\Phone', 'user_id', 'id');
    }
}

// 所属
class Phone extends Model
{
    /**
     * 获取拥有该手机的用户
     */
    public function user()
    {
        // User  : 所属的模型
        // Phone : user_id 外键
        // User  : id      父模型主键
        return $this->belongsTo('App\User', 'user_id', 'id');
    }
}

// 空模型
class Article extends Model
{
    /**
     * 获取文章作者
     */
    public function user()
    {
        return $this->belongsTo('App\User')->withDefault(function ($user) {
            $user->name = 'Guest Author';
        });
    }
}

一对多关联

// 拥有
class Post extends Model
{
    /**
     * 获取博客文章的评论
     */
    public function comments()
    {
        // Comment : 关联的模型
        // Comment : post_id 外键
        // Post    : id      主键
        return $this->hasMany('App\Comment', 'post_id', 'id');
    }
}

// 所属
class Comment extends Model
{
    /**
     * 获取评论对应的博客文章
     */
    public function post()
    {
        // Post    : 关联的模型
        // Comment : post_id 外键
        // Post    : id      父模型主键
        return $this->belongsTo('App\Post', 'post_id', 'id');
    }
}

多对多关联

// 关联
class User extends Model
{
    /**
     * 用户角色
     */
    public function roles()
    {
        // Role       : 关联的模型
        // user_roles : 中间表名称
        // user_id    : 对应到模型主键
        // role_id    : 对应到关联主键
        return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'role_id');
    }
}

// 获取中间表字段, 通过 pivot 属性
$user = App\User::find(1);
foreach ($user->roles as $role) {
    echo $role->pivot->created_at;
}

// 当 pivot 表包含额外的属性时, 必须定义关联时先指定
return $this->belongsToMany('App\Role')->withPivot('column1', 'column2');

// 自动包含created_at 和 updated_at
return $this->belongsToMany('App\Role')->withTimestamps();

// 更换 pivot 为 subscription, 提升可读性
return $this->belongsToMany('App\Podcast')
            ->as('subscription')
            ->withTimestamps();
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
    echo $podcast->subscription->created_at;
}

渴求式加载

// select * from books
$books = App\Book::all();
// select * from authors where id in (1, 2, 3, 4, 5, ...)
$books = App\Book::with('author')->get();
foreach ($books as $book) {
    echo $book->author->name;
}

// 渴求式加载多个关联关系
$books = App\Book::with('author', 'publisher')->get();

// 嵌套的渴求式加载
$books = App\Book::with('author.contacts')->get();

// 渴求式加载指定字段
// 注: 使用这个特性时, id 字段是必须列出的
$users = App\Book::with('author:id,name')->get(); 

// 带条件约束的渴求式加载
$users = App\User::with(['posts' => function ($query) {
    $query->where('title', 'like', '%first%');
}])->get();

插入 / 更新关联模型

// 插入关联模型
$comment = new App\Comment(['message' => 'A new comment.']);
$post = App\Post::find(1);
// 调用 comments 方法获取关联关系实例, save 将添加 post_id 到 Comment 模型中
$post->comments()->save($comment);

// 保存多个关联模型
$post = App\Post::find(1);
$post->comments()->saveMany([
    new App\Comment(['message' => 'A new comment.']),
    new App\Comment(['message' => 'Another comment.']),
]);

// 使用 create 创建, 与 save 不同的是, 它j接收一个关联数组, create 方法遵循模型属性的批量赋值操作
$post = App\Post::find(1);
$comment = $post->comments()->create([
    'message' => 'A new comment.',
]);

// 保存多个关联模型
$post = App\Post::find(1);
$post->comments()->createMany([
    [
        'message' => 'A new comment.',
    ],
    [
        'message' => 'Another new comment.',
    ],
]);

// 更新从属关联关系 (belongsTo)
$account = App\Account::find(10);
// associate 方法会在子模型设置外键
$user->account()->associate($account);
$user->save();

// 移除关联 (belongsTo) 
// dissociate 方法会设置关联关系的外键为 null
$user->account()->dissociate();
$user->save();

附加 / 分离多对多关联模型

$user = App\User::find(1);
// 在连接模型的中间表中插入记录
$user->roles()->attach($roleId);
// 插入数据和附加的数组到中间表
$user->roles()->attach($roleId, ['expires' => $expires]);

// 从中间表中移除相应的记录: 指定用户移除某个角色
$user->roles()->detach($roleId);
// 从中间表中移除相应的记录: 指定用户移除所有角色
$user->roles()->detach();

// attach 和 detach 还接收数组形式的 ID 作为输入
$user = App\User::find(1);
$user->roles()->detach([1, 2, 3]);
$user->roles()->attach([
    1 => ['expires' => $expires],
    2 => ['expires' => $expires]
]);

在中间表上保存额外数据

处理多对多关联时, save 方法接收中间表数组作为第二个参数:

App\User::find(1)->roles()->save($role, ['expires' => $expires]);

访问器和修改器

访问器和修改器 允许你在获取模型属性或设置其值时格式化 Eloquent 属性.

例如, 你可能想要使用 Laravel 加密器对存储在数据库中的数据进行加密, 并且在 Eloquent 模型中访问时自动进行解密.

除了自定义访问器和修改器, Eloquent 还可以自动转换日期字段为 Carbon 实例甚至 将文本转换为 JSON .

访问器

class User extends Model
{
    /**
     * 获取用户的名字
     *
     * @param  string  $value
     * @return string
     */
    public function getFirstNameAttribute($value)
    {
        return ucfirst($value);
    }

    /**
    * 获取用户的全名
    *
    * @return string
    */
    public function getFullNameAttribute()
    {
        return "{$this->first_name} {$this->last_name}";
    }
}
// 访问 first_name 属性
$firstName = App\User::find(1)->first_name;

修改器

class User extends Model
{
    /**
     * 设置用户的名字
     *
     * @param  string  $value
     * @return string
     */
    public function setFirstNameAttribute($value)
    {
        $this->attributes['first_name'] = strtolower($value);
    }
}
// 设置 first_name 属性
App\User::find(1)->first_name = 'Sally';

日期修改器

默认情况下, Eloquent 将会转化 created_atupdated_at 列的值为 Carbon 实例, 该类继承自 PHP 原生的 Datetime 类, 并提供了各种有用的方法. 你可以自定义哪些字段被自动调整修改, 甚至可以通过重写模型中的 $dates 属性完全禁止调整:

class User extends Model
{
    /**
     * 应该被调整为日期的属性
     *
     * @var array
     */
    protected $dates = [
        'created_at', 
        'updated_at', 
        'disabled_at'
    ];
}

// 自动转换并存储到数据库中
$user = App\User::find(1);
$user->disabled_at = Carbon::now();
$user->save();

// 使用 Carbon 提供的方法
$user = App\User::find(1);
return $user->disabled_at->getTimestamp();

模型日期格式

默认情况下, 时间戳的格式是 Y-m-d H:i:s , 可以结合 $dateFormat 属性自定义格式:

class Flight extends Model
{
    /**
     * 模型日期的存储格式
     *
     * @var string
     */
    protected $dateFormat = 'U';
}

属性转换

支持的转换类型: integer , real , float , double , string , boolean , object , array , collection , date , datetimetimestamp .

如果数据库有一个 JSONTEXT 字段类型包含了序列化 JSON, 可使用 array 转换, 将自动进行 序列化反序列化 .

class User extends Model
{
    /**
     * 应该被转化为原生类型的属性
     *
     * @var array
     */
    protected $casts = [
        // 转换 is_admin 属性: 从 integer (0/1) 转换为 boolean
        'is_admin' => 'boolean',
        // 访问 options 属性将会自动从 JSON 反序列化为 PHP 数组
        // 设置 options 属性的值时, 给定数组将会自动转化为 JSON 以供存储
        'options' => 'array',
    ];
}

// is_admin 属性已经被转换了:
if ($user->is_admin) {
    //
}

// 自动序列化和反序列化
$user = App\User::find(1);
$options = $user->options;
$options['key'] = 'value';
$user->options = $options;
$user->save();

我的电脑是 Ubuntu 14.04 LTS, 自己手工编译 php5.6, 打开 ZEND_EXTRA_LIBS='-liconv' 时, 发现没有安装 libiconv, 也就是编码转换的库, 所以百度该库的安装方法, 如下:

下载:

$ wget http://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.14.tar.gz

解压:

$ tar -zxvf libiconv-1.14.tar.gz
$ cd libiconv-1.14.1

安装:

$ ./configure --prefix=/usr/local
$ make
# make install

不过我make的时候出现了一个问题:

n file included from progname.c:26:0:
./stdio.h:1010:1: error: ‘gets’ undeclared here (not in a function)
_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
^
make[2]: * [progname.o] Error 1
make[2]: Leaving directory `/home/freeman/Downloads/libiconv-1.14_2/srclib'
make[1]: * [all] Error 2
make[1]: Leaving directory `/home/freeman/Downloads/libiconv-1.14_2/srclib'
make: * [all] Error 2

原因未明, 应该是软件的bug吧, 后来百度找到了 解决方法, 整理如下~

切换到srclib目录下:

$ cd srclib

修改stdio.in.h文件:

$ gedit stdio.in.h

定位到

_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");

这一行, 改成:

#if defined(__GLIBC__) && !defined(__UCLIBC__) && !__GLIBC_PREREQ(2, 16)
 _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
#endif

注意粘贴完下面有两行 #endif, 别少复制了一行 #endif, 改完是这个样子滴~别忘了保存~

#if defined(__GLIBC__) && !defined(__UCLIBC__) && !__GLIBC_PREREQ(2, 16)
 _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
#endif
#endif

保存, make 就可以了, make install 完别忘了

# ldconfig

记下来, 下次就不会忘记了~

本文于 2017-12-05 重新整理.

写了一个可以对 $_GET, $_POST 等输入进行过滤的函数,递归实现如下:

function array_map_recursive($filters, $data)
{
    $result = [];
    foreach ($filters as $filter)
    {
        foreach ($data as $key => $value)
        {
            $result[$key] = is_array($value) 
                ? array_map_recursive($filters, $value)
                : call_user_func($filter, $value);
        }
    }
    return $result;
}

后来想想,以前看书的时候经常说递归函数会浪费堆栈空间,甚至会导致堆栈溢出,于是重新用循环实现了这个函数:

function array_map_all($filters, $data)
{
    foreach ($filters as $filter)
    {
        $stack = [];
        $stack[] = &$data;
        while ( ! empty($stack))
        {
            $node = &$stack[count($stack) - 1];

            array_pop($stack);

            if ( ! is_array($node))
            {
                $node = call_user_func($filter, $node);
            }
            else
            {
                foreach ($node as $key => $value)
                {
                    if ( ! empty($value))
                    {
                        $stack[] = &$node[$key];
                    }
                }
            }
        }
    }
    return $data;
}

写完哇擦~多了这么多行~还是递归简洁明了:)

速度如何呢?写个代码测试一下!

$filters = ['trim', 'htmlspecialchars'];
$data = [
    ' 5 &',
    [' 3 ', ' 7 '],
    [' 2 ', ' 4 '],
    ' 15 ',
    ' 4 ',
    ['12 ', '  ', ' 3 ', ' 4'],
];

$begin = microtime(true);
for ($i = 0; $i < 10000; $i++)
{
    array_map_recursive($filters, $data);
}
$t1 = microtime(true) - $begin;
echo '递归用时: '.sprintf('%.4f', $t1)."\n";

$begin = microtime(true);
for ($i = 0; $i < 10000; $i++)
{
    array_map_all($filters, $data);
}
$t2 = microtime(true) - $begin;
echo '循环用时: '.sprintf('%.4f', $t2)."\n";

运行结果:
递归用时: 0.4985
循环用时: 0.5489

换个复杂的七维数组,如下:

$data = [
    ' 5 &',
    [' 3 ', ' 7 ', [' 55 ', ['    565 ', [' 5445 ', ['da ', [', d '], '3']], 'd d']]],
    [' 2 ', ' 4 '],
    ' 15 ',
    ' 4 ',
    ['12 ', '  ', ' 3 ', ' 4'],
];

运行结果:
递归用时: 5.7484
循环用时: 0.9799

嘿嘿~结果不用我说了吧!还是循环好一些,不过实际应用中,是感觉不到太大差别的,毕竟是10000次循环呐!更何况是一个七维数组呢~