PHP and laravel知识点小小积累

function () use ($x, &$y){}

自从PHP5.3开始有了closure/匿名函数的概念,在这里的use关键词的作用是允许匿名函数capture到父函数scope

内存在的$x和$y变量。其中&&y为引用方式capture,也就是说每次该匿名函数调用时,y的值如果

被修改了也反映在这里,而$x则是静态引用。

<?php
$message = "hello\n";


$example = function () {
    echo $message;
};
// Notice: Undefined variable: message
$example();


$example = function () use ($message) {
    echo $message;
};
// "hello"
$example();


// Inherited variable's value is from when the function is defined, not when called
$message = "world\n";
// "hello"
$example();


// Inherit by-reference
$message = "hello\n";
$example = function () use (&$message) {
    echo $message;
};
// "hello"
$example();
// The changed value in the parent scope is reflected inside the function call
$message = "world\n";
// "world"
$example();


// Closures can also accept regular arguments
$example = function ($arg) use ($message) {
    echo $arg . ' ' . $message;
};
// "hello world"
$example("hello");

 PSR0,PSR2,PSR4

psr2是一种编码规范,

PSR0,PSR4是PHP的autoloading机制中的目录安排规范

详情: 

https://github.com/php-fig/fig-standards/tree/master/accepted

laravel中计划设计编码一个小功能的流程

1.规划设计routes: /tasks ; /alice/tasks 

Route::get('/tasks','TasksController@index');

2.创建上述controller

php artisan make:controller TasksController
Controller created successfully.
app/Http/Controllers/TasksController.php created

3.创建Task model

$ php artisan make:model Task
Model created successfully.

4. 创建tasks表

$ php artisan make:migration create_tasks_table --create --table="tasks"
Created Migration: 2016_04_09_134106_create_tasks_table
$ php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2016_04_09_134106_create_tasks_table

注意:由于laravel自带了users和password_resets两个migration,所以我们执行php artisan migrate时有了3个表被创建

5. 创建view并在blade模版视图resources.tasks.index中引用模型数据

        @foreach($tasks as $task)
            <li>
                <a href="{{ url('/tasks',$task->id) }}">{{ $task->title }}</a>
            </li>
        @endforeach

6.在控制器中获取model数据并且assign给blade模版视图resources.tasks.index

class TasksController extends Controller
{
    public function index(){
        $tasks = Task::all();
        return View::make('tasks.index',compact('tasks'));
    }
}

至此,tasks index 页面的功能已经设计完毕。

从第5步骤我们看到,由于我们在视图中对每一个task都创建了对应的链接,而该链接的页面url为/tasks/{id},因此从这里我们会发现我们接下来的工作是设计show页面!@!

7.回到routes.php,我们创建show页面的路由:

Route::get('/tasks/{task}','TasksController@show');

8,这时我们在index页面点击链接时会报错“TasksController::show() does not exist”, 这也就告诉我们需要创建show方法

    public function show(Task $task){
        return View::make('tasks.show',compact('task'));
    }

注意在这里我们使用了laravel5提供的route model binding特性,我们在控制器中使用Task类typehinting了$task参数,而该$task参数和routes.php中定义的wildcast路由Route::get('tasks/{task}','xxx'}定义的task相匹配,因此laravel在调用我们的控制器时自动注入Task模型(以id为索引)。这个功能后续再做进一步的深入。

9,这时我们剩下来的工作就是设计show页面模版了:

        <div class="task">
            <h1>{{$task->title}}</h1>    
            <h2>{{$task->description}}</h2>
        </div>

至此,我们就完成了一个简单的task  crud的一部分功能的完整开发了。

但是上述两个页面存在html重复的问题,我们可以抽象出一个layout模版,由tasks.index和tasks.show两个页面来共享

使用php定界符完成大片字符和变量连接

<?php

$name = 'kitty';

$htmlstring = <<<Eof

<table height="20">

<tr><td>

{$name}<br/>

<script>

var p='hello world';

document.writeln(p);

</script>

</td></tr>

</table>

Eof;

 

允许laravel程序执行sudo shell脚本

可以参考http://www.4wei.cn/archives/1001469详情

有一点需要注意应该使用命令的全名称(包含路径),否则可能出问题: 执行sudo命令时command not found的解决办法

编辑sudoers文件,注释掉Defaults requiretty这行
否则会出现sudo: sorry, you must have a tty to run sudo的错误

再添加一行:

  apache ALL=(ALL) NOPASSWD:ALL

这行中apache是laravel运行时的用户名,如果你不清楚到底apache/ngix用户名是什么可以用php的echo shell_exec("id -a")打印出来
这一行主要解决使用sudo命令时要求输入密码,而我们在网站程序中不可能输入密码的

创建resource controller

$php artisan make:controller --resource task/TasksController

resource controller中默认产生以下几个路由项:

+--------+-----------+--------------------+---------------+----------------------------------------------+------------+
| Domain | Method    | URI                | Name          | Action                                       | Middleware |
+--------+-----------+--------------------+---------------+----------------------------------------------+------------+
|        | GET|HEAD  | tasks              | tasks.index   | App\Http\Controllers\TasksController@index   | web        |
|        | POST      | tasks              | tasks.store   | App\Http\Controllers\TasksController@store   | web        |
|        | GET|HEAD  | tasks/create       | tasks.create  | App\Http\Controllers\TasksController@create  | web        |
|        | DELETE    | tasks/{tasks}      | tasks.destroy | App\Http\Controllers\TasksController@destroy | web        |
|        | PUT|PATCH | tasks/{tasks}      | tasks.update  | App\Http\Controllers\TasksController@update  | web        |
|        | GET|HEAD  | tasks/{tasks}      | tasks.show    | App\Http\Controllers\TasksController@show    | web        |
|        | GET|HEAD  | tasks/{tasks}/edit | tasks.edit    | App\Http\Controllers\TasksController@edit    | web        |
+--------+-----------+--------------------+---------------+----------------------------------------------+------------+

 laravel csrf

laravel中web middle会对Post操作做保护,必须有一个csrf的隐藏域放在form post data中,laravel才允许向下走。为了让laravel通过这个保护验证,我们需要在form中通过{{ csrf_field() }}来完成这个数据的存放:

{{ csrf_field() }}
//会在form中产生以下field:
<input type="hidden" name="_token" value="cbgekcEhbraIiU0ZaeNFgyQ5OIpg8xjIpBb7AXv9">

laravel REST API的put,patch,delete操作的实现

由于所有浏览器对于form提交时只支持get和post两种method,而我们的REST API定义是由put,patch,delete,get,post完备的五种方法来实现的,一个workaround方案是和csrf类似,增加一个隐藏field , _method,

laravel也提供了对应的helper函数来完成这个工作:

{{ method_field('delete') }}
上面的blade模板编译为:
<input type="hidden" name="_method" value="delete">

这样操作以后,即使我们提交表单时使用的是post方法,但是laravel会检查这个_method field,根据其对应的value来匹配到对应的路由,并且route到对应的控制器方法:比如update和destroy方法

laravel blade忽略{{}}的方法

有时候,我们不希望在laravel的模版引擎中解析对应的{{}}内部的内容,比如vuejs,angularjs都可能使用{{}}作为他们的前端模版的输出函数,这时,我们就希望blade不要多此一举地在服务端渲染,这个{{}}留到前端去解析渲染吧。很简单只要加上@{{}},laravel就会忽略这里的{{}}

@{{ data_to_web_page_for_javascript }}

laravel自带的前端编译工具组件elixr/browserify/vuejs等开发工具安装时问题解决

在一个全新的laravel项目下载后,通过执行npm install来安装构建工具,windows下可能在安装过程中会出现问题,一个可能的原因是你的git bash/cmd运行于administrator用户下,而gulp-sass在安装时只能用普通用户权限下安装。

另外一点是在安装该工具后,有可能你有自己的gulpfile,这时希望两者都可用的话,可以通过--gulpfile来指定你自己的或者laravel自带的gulpfile

gulp --gulpfile yourgulpfile.js

 如果在执行npm install时,由于git默认情况下对文件名的长度是有限制的,那么就有可能node module嵌入深度过长导致git add出错:

fatal: cannot create directory at 'laravel-elixir-vueify/node_modules/babel-preset-es2015/node_modules/babel-plugin-transform-es2015-block-scoping/
node_modules/babel-traverse/node_modules/babel-code-frame/node_modules/chalk/node_modules/ansi-styles': Filename too long

可以通过设置git core的核心参数longpaths 为true打开git不限制文件长度的核心功能

[core]
    autocrlf = true
    filemode = false
    longpaths = true

 bootstrap/cache/service.php无法写入的问题

ErrorException in Filesystem.php line 109:
file_put_contents(/assets/www/newkidsitl5229/bootstrap/cache/services.php): failed to open stream: Permission denied

解决办法:

php artisan cache:clear

 laravel/PHP中的static::和self::

static::引用的是全局作用域,而self::引用的是对本类的静态方法的引用。比如一个类中定义了static方法或者属性,子类中又重写了这个static属性,则static::staticProperty就引用的是子类的重写值,而selft::staticProperty则返回在自己类作用域中的static

参考:

https://segmentfault.com/q/1010000002468880

new static($user) vs new self

class A {
  public static function get_self() {
    return new self();
  }
 
  public static function get_static() {
    return new static();
  }
}
 
class B extends A {}
 
echo get_class(B::get_self()); // A
echo get_class(B::get_static()); // B
echo get_class(A::get_static()); // A

http://www.jb51.net/article/54167.htm

self - 就是这个类,是代码段里面的这个类。

static - PHP 5.3加进来的只得是当前这个类,有点像$this的意思,从堆内存中提取出来,访问的是当前实例化的那个类,那么 static 代表的就是那个类。

static和$this很像,代表的是子类,但是又可以应用于用静态的方法

oauth2第三方登录

我们可以使用安正超的laravel-socialite package轻松实现第三方登录,然而在使用过程中,可能会由于CA的配置出现以下错误:

“cURL error 60: SSL certificate problem: unable to get local issuer certificate”

解决方案:

到以下网址: http://curl.haxx.se/ca/cacert.pem 拷贝整个网页内容,随后paste为一个文件 "cacert.pem", 更改php.ini文件中的

curl.cainfo = "[pathtothisfile]\cacert.pem"

最后重新启动你的php,即可解决!

常用oauth2第三方登录:

qq需要qq互联开通应用; http://connect.qq.com/

微信需要 https://open.weixin.qq.com

laravel5.2 session丢失问题

在开发鉴权模块时,当使用Auth::login($user),随后dd(Auth::user())虽然可以打印出登录的用户信息,但是重新到另外一个page却发现打印dd(Session::all())时,仅仅打印出_token的值,并且每次重新刷新都会变化。

这个困扰了我有好几个小时,后来发现是dd()调用惹的祸,由于dd会die(),而laravel使得session数据保存是在index.php文件的最后

$kernel->terminate($request, $response);来实现的,因此dd()调用时可能无法执行到此,因而session数据每次执行时都会丢失!!更改为var_dump后就好了!

还有以下几点值得注意:

1. 不要var_dump,如果在代码中有var_dump,每次刷新页面都会生成一个新的session文件!

2. 在routes.php中默认定义的都被包在'web' middleware中了(由RouteServiceProvider自动包含的!!),因此你不要在routes.php中再包一层'web' middleware了,或者你是一个偏执狂,你可以这样用:

Route::group(['middlewareGroups'=>'web'],function(){
    Route::get('/', function () {
        return view('welcome');
    });
});

对于api类型的route,你可以这样安排:

a).仍然在routes.php中,你使用'api'这个middle group;

b.)放到另外一个文件中,比如叫做routes_api.php,并且在RouteServiceprovider中来引用!

具体请参考这个url: https://github.com/laravel/framework/issues/13000

PHP trait中的insteadof 关键字

由于PHP是单继承语言,不支持从多个基类中继承,php为了克服这个弱点,引入了trait的概念,使用trait我们可以不用使用传统的继承方式来继承基类的方法,我们可以使用 use trait来直接引用到公共的方法。

一个PHP类可以use多个trait,这些trait中有可能会有命名冲突,这时我们可以通过insteadof关键字来指明我们使用哪个:

trait AuthenticatesAndRegistersUsers
{
    use AuthenticatesUsers, RegistersUsers {
        AuthenticatesUsers::redirectPath insteadof RegistersUsers;
        AuthenticatesUsers::getGuard insteadof RegistersUsers;
    }
}

关于trait更加详细的描述,可以参考 http://www.cnblogs.com/CraryPrimitiveMan/p/4162738.html

Route group with namespace

有时我们希望将一组路由作为一个集合,这时可以给这个组一个namespace,以免每个controller都输入其namespace的麻烦:

Route::group(['prefix'=>'admin','namespace'=>'Admin'],function(){
   Route::get('/', AdminController@index); 
})

named route

在laravel中,如果我们直接引用一个绝对url,这本来没有什么问题,但是如果后面该url做了更改,那么就需要很多地方来做更改,一个可行的方案是使用named route:命名路由:

Route::get('session/create',['as'=>'register','uses'=>'SessionCotroller@index']);

这样其他地方使用link_to_route('register')则无论我们怎么改变上面的url,这个route都能链接到正确的url!

http_build_query

在laravel form操作中,经常需要对post数据格式化,其中get method下往往需要根据相关数据形成一个query string,这时可以利用php自带的http_build_query函数来实现:

<?php
$data = array('foo'=>'bar',
              'baz'=>'boom',
              'cow'=>'milk',
              'php'=>'hypertext processor');

echo http_build_query($data) . "\n";
echo http_build_query($data, '', '&amp;');

?>

//输出以下内容:
foo=bar&baz=boom&cow=milk&php=hypertext+processor
foo=bar&amp;baz=boom&amp;cow=milk&amp;php=hypertext+processor

 

password_hash/password_verify

对于密码字段我们往往不希望明码保存,在PHP中原生提供了对password加密及验证的函数password_hash和password_verify.其中hash支持两种默认的算法:PASSWORD_DEFAULT和PASSWORD_BCRYPT

/**
 * 在这个案例里,我们为 BCRYPT 增加 cost 到 12。
 * 注意,我们已经切换到了,将始终产生 60 个字符。
 */
$options = [
    'cost' => 12,
];
echo password_hash("rasmuslerdorf", PASSWORD_BCRYPT, $options)."\n";

 对应laravel中的facade及其函数是Hash::make(),Hash::check(),其底层使用了

github.com/ircmaxell/password_compat这个package

Redirect::intended()

对于保护的路由,我们有这样的需求:一旦用户登录并且有权访问这个路由的话,我们希望直接redirect到先前的这个路由,这里Redirect::intended()就是满足这个场景的。

if(Auth::attempt([
 'email' =>$input['email'],
 'password' =>$input['password']
]) return Redirect::intended('home');

上面的代码中如果登录成功,则重定向到先前的page,或者是home页面

Redirect::back()

一般用于登录未成功时返回前一页面

flash message

当系统中发生了特定的事件,比如login成功时,我们可能需要给用户一个提示。实现方式:

1. 在controller中redirect的同时,调用with('flash_message', 'some information');

if(Auth::attempt([
 'email' =>$input['email'],
 'password' =>$input['password']
]) return Redirect::intended('home')->with('flash_message','You are logged in!~~');

在模板中:

@if (Session::get('flash_message'))
  <div class = "flash">
     {{ Session::get('flash_message' }}
  </div>
@endif

 

2. 在模板中通过@if Session::get('flash_message')来判断是否有flash message,如果有则显示出来

Mass Assignment protection

在laravel中,controller接收到form数据后,非常常见的一个使用方法就是

User::create(Input::all());这种模式虽然创建新user时非常方便,但是对于hacker来说,提供了一种非常便利地修改后台数据的方法,比如在user create form中,除了username,password外,hacker可能会在客户端增加一个hidden field: active,在用户提交username,password的同时,将active也设置为true,这时由于我们后台代码未作保护,则自动把该user就创建为active的用户了,而这,可能并不是我们想要的。解决方法: 1.要么不要使用User::create(Input::all)调用模式,我们使用$user = new User; $user->username = Input::get('username');$user->password=Input::get('password'),这样虽然form中有active字段,但是我们却弃之不用这样就达到了保护的目的;

2.上面的方法虽然可行,但是如果一个form表单数据非常多,一条一条地这样调用也显得麻烦,但是同时又要防止上面未经同意的数据注入,可以使用laravel model的$guarded和$fillable

class User extends Eloquent{
  protected $guarded = ['active']; //指定哪些字段不能被mass assignment
  protected $fillable = ['username','password'] ;  //指定哪些字段可以mass assign 
  
}

 自动将password进行hash化后再保存

对于密码这类敏感信息,我们不要使用明文保存在数据库中,但是每次调用save时都显式调用Hash::make($password)也是一个比较繁琐的事情,好在laravel的eleoquent modle提供了一个这样的feature: 如果在model类中有setPasswordAttribute($password)这样的方法存在,则在save之前laravel自动调用这个方法来格式化$password,同样地,如果有getPasswordAttribute()方法的话,则每次访问一个字段时,都会调用getXXXXAtttribute()函数,其返回值作为真正获取到的值。

class User extends eloquent{
 public setPasswordAttribute($password){
   $this->attributes['password'] = Hash::make($password);
}

}

 Route::resource only参数

在定义路由时,我们使用Route::resource('sessions','SessionsController')虽然是一个不错的选择,但是有时候我们可能只对其中的两三个方法感兴趣,这时,我们可以使用only参数来明确指明只需要创建这几个路由,其他的都忽略掉:

Route::resource('sessions','SessonsController',['only'=>['create','store','destroy']);

 Illuminate\Database\Eloquent\Collection and User::first() and User::all()->first()

参考Arrays on Steroids.mp4

User::all()将返回上述Collection对象,该对象又实现了多个interface: ArrayAccess, ArrayableInterface, Countable, IteratorAggregate, JsonableInterface,也就是说返回的对象支持多种操作方式

>>> $users = App\User::all(); //在这里会执行数据库操作
=> Illuminate\Database\Eloquent\Collection {#690
     all: [],
   }
>>> count($users);
=> 0
>>> count($users);
=> 0
>>> count($users);
=> 0
>>> $users = App\User::all();
=> Illuminate\Database\Eloquent\Collection {#701
     all: [
       App\User {#702
         id: 6,
         name: "cnwedd",
         email: "dasd@ff.cc",
         created_at: null,
         updated_at: null,
         realname: "",
         imageurl: "",
         lastlogin: "2016-05-02 13:06:06",
         mobile: "",
       },
     ],
   }
>>> count($users);
=> 1
>>> $users->toJson();
=> "[{"id":6,"name":"cnwedd","email":"dasd@ff.cc","created_at":null,"updated_at":null,"rea
lname":"","imageurl":"","lastlogin":"2016-05-02 13:06:06","mobile":""}]"
>>>

$users->first()->toArray(); = User::first()->toArray之间有什么区别?

$users->last()->toArray(); = User::last()->toArray之间有什么区别?

$users->all()则不再访问数据库,因为User::all()已经返回了数据

 

 App\User::first(['name']);
=> App\User {#704
     name: "cnwedd",
   }
//注意这个操作会执行数据库查询,并且只获取name字段

我们也可以使用laravel的collection support helper类来使得任意数组变成一个方便操作的collection:

$myusers = new Illuminate\Support\Collection($myuserarray);

随后$myusers->first()/last()/toArray()等等

使用Form::macro快速构建form

 注意FormBuilder从5.1开始已经不再在框架core中出现,由https://laravelcollective.com/docs/5.2/html 来维护。

这其中包括: linke_to_route等Helpe函数

 laravel model cache

对于不经常变化而又频繁访问的数据,最好把这些数据cache起来,这样每次访问这些数据时,直接从cache中获取而不用访问数据库,这会大大提高应用的性能。在laravel中起用cache非常简单:

$users = User::remember(10,'users.all')->get();
//获取users表中的所有数据,并且保存在cache中10分钟,cache的key为users.all
//cache默认使用file方式,你也可以使用memcache

 如果你需要强制清除上面这个key的cache,则可以使用:

Cache::forget('users.all'); //该代码只清除'users.all'这个key的cache内容
Cache::flush();//该代码将所有cache都清除掉=php artisan cache:clear

参考:6-Cache Tags and Memcached.mp4

laravel cookie, session,cache

Cookie保存在客户的计算机上;Session保存在服务端;Cookie和Session都是对用户的,而cache则是系统级别的;

Cookie::forever('key','value');

Cookie::get('key');

Session::put('foo','bar');

Session::get('foo');

Cache::put('favorites',[1,2], Carbon\Carbon::now()->addMinutes(60));

Cache::get('favorites');

laravel upload file

在laravel中,upload文件时需要注意一点是在Form::open中需要指定'files'=>true

{{ Form::open(['route' =>'posts.store', 'files'=>true }}

<input type="file" name="thumbnail">
.... {{ Form::close() }}

注意上面的'files'=>true,FormBuilder会自动在form标签中添加 enctype="multipart/form-data"属性!

Input::file('thumbnail')将返回一个Symfony\Component\HttpFoundation\File\UploadedFile对象,你可以使用该对象的move方法将上传的文件转存

public function store(){
$post = new Post;
$post->title = Input::get('title');
$post->body = Input::get('body');
if (Input::hasFile('thumbnail')){
$file = Input::file('thumbnail'); $file= $file->move(public_path().'/images/','myfile.img');//将上传的文件重命名为myfile.img并且存放到public/images目录中
$post->thumbnail = $file->getRealPath();//返回绝对地址;或者使用getClientOriginalName返回文件名;
}
$post->save();
return "done";
}

 $table->date('created_at')->nullable();

https://laravel.com/docs/5.2/migrations#creating-columns

database seeder and faker

在项目开发中,很多时候需要创建一些dummy data,方便快速原型开发,这时可以使用laravel的db seeder类,以及第三方faker库

参考10-Seeds-and-Fakes.mp4.mp4

softDelete

有时我们不希望直接对数据库执行永久删除的操作,比如一个用户我们可以deactivate,随后当需要的时候我们再把他找回来,这时候就需要使用softDelete的功能了。

很简单只要在对应的model中添加一个protected字段:

class User extends Eloquent{
 protected $table = 'users';
 
 portected $softDelete = true; //原理是:当执行User::delete(1)时,laravel只在数据库的deleted_at字段更新了内容
//随后在查询数据库User::all()时并不会返回任何内容,
//除非使用User:withTrashed()->all()才会返回
}

$user = User::withTrashed()->find(2);

$user->restore();

$user->posts()->restore();

在上面三行代码例子中,我们对$user和$user的posts都设置了softDelete

如何通过PHP代码实现Cascade delete, update

虽然通过类似于下面的代码,可以借用mysql数据库的联动功能实现foreign key关联的资源自动删除,但是由于不是每一种数据库都有这种功能,故而我们可以通过php代码来提供统一的方案:

        Schema::table('class_student', function (Blueprint $table) {
            $table->foreign('class_id')->references('id')->on('classes')
                ->onUpdate('cascade')->onDelete('cascade');
            $table->foreign('student_id')->references('id')->on('users')
                ->onUpdate('cascade')->onDelete('cascade');
            $table->primary(['class_id','student_id']);
        });

参考: http://stackoverflow.com/questions/34363224/laravel-cascade-not-working

protected static function boot() {
    parent::boot();
    static::deleting(function($ticket) {
        // delete related stuff ;)
        $reaction_ids = $ticket->reactions()->lists('id');
        Reaction::whereIn($reaction_ids)->delete();
    });
}

 

Form::model

当我们在edit一个资源时,我们可能希望edit form自动Populate数据库中的内容,这时只需要将

Form::open替换为Form::model并且传入对应的model即可,

比如{{ Form::model($user, ['method'=> 'PATCH', 'route'=> ['users.update', $user->id]]) }}

覆盖默认relation所检索的字段

laravel中存在着一套默认的命名规约,比如project和user之间的relation,project属于一个用户,

我们在Project model中可以创建一个owner方法,该方法返回该project所属的用户,默认情况下,laravel会使用owner_id作为外键,但是这肯定不是我们所要的,我们可以传入第二个参数'user_id'即可解决这个问题:

class Project extends Eloquent{
 protected $fillable = ['user_id','title','description'};
 public function owner(){
  return $this->belongsTo('User','user_id');//注意默认情况下laravel会使用owner_id  
}
}

监听数据库查询,打印相关query,优化性能

laravel model非常强大易用,通过简单的一两行代码我们就可以创建强大的关系结构,但是随着应用复杂度增大,系统的性能可能快速下降,这时通过监察系统对数据库查询的频率就可以对优化有一些思路:

Event::listen('illuminate.query',function($sql){
  var_dump($sql); //通过监听illuminate.query事件,就能大概搞清楚系统的瓶颈,对于relation操作往往会有一个N+1 problem可以优化
});

我们通过with方法一次性地取出数据记录同时取出对应的relation数据,则可以大大优化数据库查询的次数:

$projects = Project::with('owner')->remember(10)->get();

上面的代码只需要执行2次数据库查询,同时放到cache中10分钟,这将大大提高系统的性能.

laravel全文检索

$query = Request::get('q');
$posts = Post::where('title','LIKE', "%$query%")->get();
//将返回title中包含$query字符串的post

REST API nested resource

使用laravel构建REST API是非常常见的应用,laravel也提供了一种构建这种应用路由框架的简单方法: route:resource('resourcename','controllername');但是很多情况下,我们可能需要嵌入式资源,比如user, user.task,

具体使用方法如下:

Route::resource('users','Auth\AuthController');
Route::resource('users.tasks','TasksController');

上面两行代码将在laravel中形成以下标准的url路由:

|        | POST      | users                            | users.store         | App\Http\Controllers\Auth\AuthController@store                         | web,guest  |
|        | GET|HEAD  | users                            | users.index         | App\Http\Controllers\Auth\AuthController@index                         | web,guest  |
|        | GET|HEAD  | users/create                     | users.create        | App\Http\Controllers\Auth\AuthController@create                        | web,guest  |
|        | PUT|PATCH | users/{users}                    | users.update        | App\Http\Controllers\Auth\AuthController@update                        | web,guest  |
|        | GET|HEAD  | users/{users}                    | users.show          | App\Http\Controllers\Auth\AuthController@show                          | web,guest  |
|        | DELETE    | users/{users}                    | users.destroy       | App\Http\Controllers\Auth\AuthController@destroy                       | web,guest  |
|        | GET|HEAD  | users/{users}/edit               | users.edit          | App\Http\Controllers\Auth\AuthController@edit                          | web,guest  |
|        | POST      | users/{users}/tasks              | users.tasks.store   | App\Http\Controllers\TasksController@store                             | web        |
|        | GET|HEAD  | users/{users}/tasks              | users.tasks.index   | App\Http\Controllers\TasksController@index                             | web        |
|        | GET|HEAD  | users/{users}/tasks/create       | users.tasks.create  | App\Http\Controllers\TasksController@create                            | web        |
|        | DELETE    | users/{users}/tasks/{tasks}      | users.tasks.destroy | App\Http\Controllers\TasksController@destroy                           | web        |
|        | PUT|PATCH | users/{users}/tasks/{tasks}      | users.tasks.update  | App\Http\Controllers\TasksController@update                            | web        |
|        | GET|HEAD  | users/{users}/tasks/{tasks}      | users.tasks.show    | App\Http\Controllers\TasksController@show                              | web        |
|        | GET|HEAD  | users/{users}/tasks/{tasks}/edit | users.tasks.edit    | App\Http\Controllers\TasksController@edit                              | web        |

除此之外,可能还不够,比如我们可能需要对结果过滤,这时可以在对应url上添加query string来实现更加复杂的url结构GET /users/5/tasks?status=completed

 Dependency Inversion

高层代码不要依赖于实体对象,而应依赖于abstractions;

高层代码:不关心具体的细节;

底层代码:关心具体的细节;

一个类不要被强制依赖于具体的底层实现细节;相反应该依赖于一个contract,或者说依赖于一个interface;

比如:你的房子有一个电源接口,所有可以插电的设备,比如TV,电灯,空调等如果需要使用电源,就必须conform to(遵循)电源这个接口的规范,再看下面的代码:

interface ConnectionInterface{
  public function connect();
}
class DbConnection implements ConnectionInterface{
  
}

class PasswordReminder{
   private $dbConnection;
   public function __construct(MySQLConnection $dbConnection){
    $this->dbConnection = $dbConnection;
   //这里应该更改为:
   public function __construct(ConnectionInterface $dbConnection){
   $this->dbConnection = $dbConnection;
}
}
   
}
//需要问一下:为什么PasswordReminder需要关心是MySQLConnection,而不是SQLServer?
//也就是说PasswordReminder不应该关心具体这个Connection是什么

 IoC container

IoC container is a mini-framework for managing the composition of complex objects

Model event

当保存一个model时会有saving事件发生,对应的saving函数将被调用

Order::saving(function($model){
dd($modal);  //Order model在保存时会调用这个函数
return false; // 如果返回false,则通知laravel不要保存这个model
return $model->validate(); // 利用上面特性,我们在model中定义一个validate方法,只有检查通过才能保存!
}

但是上面的代码组织显然不是很好,我们可以放到model的类定义中:

class Order extends Eloquent{

  public static function boot(){
   parent::boot();  //boot函数在Order创建时会被调用
   static::saving(function($model){
    return $model->validate();  
  });
 }
}

 使用cache将全站静态化

思路:使用before/after filter,以url为key来做缓存,如果不存在则保存$response->getContent() (返回渲染好的html文件),如已存在则直接返回。 Caching Essentials.mp4

laravel的Exception处理

function do_something(){

  //do some function 

  throw new Exception('I take exception to that.');
}

Route::get('/', function(){
 try{
    do_something();
 }
catch(Exception $e){
   return $e->getMessage(); //获取上面抛出异常时的消息
}
});

blade中不escape html的方法

在blade模版中,默认情况下{{ $html }}将会把$html变量纯文本展示,如果你希望 $html变量中的html代码作为页面html的一部分,则需要使用

{!! $html !!}  //将$html的内容原封不动(不做escape操作)地输出到view中

 laravel使用php artisan命令创建带namespace的model

php artisan make:model Models\\RBAC\\Role
//上述命令将在app/Models/RBAC目录下创建Role模型,牛的是:如果目录不存在会自动创建!

使能Redis cache store driver

Redis是一款提供in-memory快速缓存服务的软件组件,另一方面她也提供将内存中的数据永久化的手段:即写到磁盘中,每次重新启动后直接从磁盘中恢复。要用她:

1. 首先需要安装这款软件,windows上从https://github.com/MSOpenTech/redis/releases下载安装,linux下直接从源代码build安装;

参考http://www.cnblogs.com/linjiqin/archive/2013/05/27/3101694.html windows下的安装测试

2. 在config/cache.php中选择默认的cache driver为redis

 'default' => env('CACHE_DRIVER', 'redis'),

3.你还需要通过composer安装一个php package predis/predis package (~1.0)(该package实际上就是redis client的php实现,相当于redis-cli中直接执行对应的redis 协议命令)

 

composer require predis/predis:~1.0

laravel关于redis的配置信息放在config/database.php中的redis section中,主要就是host,port,password参数,这些参数对于Redis::put/get操作时在初始化tcp连接时会使用;

同时laravel又统一做了封装放在Cache这个facade中(从而可以提供更高级的功能,比如指定cache timeout的时间为10分钟,Cache::put('foo','bar',10)),只要config/cache.php中的driver选择为redis(最好我们还是在.env文件中来定义):

CACHE_DRIVER=redis
SESSION_DRIVER=redis

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

在linux下安装 :

1. wget http://download.redis.io/releases/redis-3.0.7.tar.gz;

2. tar xvfz redis-3.0.7.tar.gz;

3. cd redis-3.0.7

4; make && make install;

5. cp redis.conf /etc/redis

6. 更改redis.conf中的daemonize yes

7. redis-server /etc/redis/redis.conf

8. 在/etc/init.d/目录创建redis启动脚本,代码如下:

#!/bin/sh
# chkconfig:   2345 90 10
# description:  Redis is a persistent key-value database
# redis    Startup script for redis processes
# processname: redis
redis_path="/usr/local/bin/redis-server"
redis_conf="/etc/redis/redis.conf"
redis_pid="/var/run/redis.pid"
# Source function library.
. /etc/rc.d/init.d/functions
[ -x $redis_path ] || exit 0
RETVAL=0
prog="redis"
# Start daemons.
start() {
if [ -e $redis_pid -a ! -z $redis_pid ];then
echo $prog" already running...."
exit 1
fi
echo -n $"Starting $prog "
# Single instance for all caches
$redis_path $redis_conf
RETVAL=$?
[ $RETVAL -eq 0 ] && {
touch /var/lock/subsys/$prog
success $"$prog"
}
echo
return $RETVAL
}
# Stop daemons.
stop() {
echo -n $"Stopping $prog "
killproc -d 10 $redis_path
echo
[ $RETVAL = 0 ] && rm -f $redis_pid /var/lock/subsys/$prog
RETVAL=$?
return $RETVAL
}
# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status $prog
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if test "x`pidof redis`" != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|status|restart|condrestart}"
exit 1
esac
exit $RETVAL

9.设置为开机启动:

chkconfig --add redis
chkconfig --level 2345 redis on

service restart redis

通过Redis::publish/subscribe实现实时通信

思路:laravel中在一个channel中publish一条消息,在Nodejs中subscribe这个channel,一旦laravel publish了这条消息,nodejs对应的subscribe会被执行,并且broadcast到所有的socket.io.js建立的connection

实现/user/username来访问users/id

一般地,对于user资源,我们都是使用id来访问的,但是这种url是非常不友好的,我们可以简单做一下修改,在laravel中就能实现以用户名作为url的参数,这样看起来就方便多了,主要有几个步骤:

1. 在路由中做wildcast , 比如Route::get(users/{username},'usersController@show'}

2. 在RouteServiceProvider的boot方法中注册路由模型绑定:

$router->bind( 'username',
            function ( $username )
            {
                return \App\User::where( 'name', $username )->firstOrFail( );
            }
        );

通过上面第2.步,则laravel在接收到/users/yourname这条路由时,就会自动查找name为yourname的User,并且返回这个model,在你的控制器中注入使用!

javascript中使用动态变量名

在项目开发中,有一个场景我需要使用动态变量名称给一个data object增加属性,随后post到服务器端,这时务必使用obj[dynamicvariablename]这种格式才会起作用:

postdata._token = token;
postdata._method = method;
postdata[this.fieldname] = inputdata;
this.$http.post(this.url,postdata).then(
。。。
)

 laravel自动对request表单数据验证的方法

1. php aritisan make:request userLoginRequest;

2. 在该类中,定义$rules;

3. 在controller中type hint,laravel自动会在收到这个post请求时做validation;

CSS子元素撑开不定高父元素增加一个mask效果的办法

思路:在父元素中嵌入一个专门用于mask的元素,绝对定位

http://demo.doyoe.com/css/auto-height/

laravel-exlixir多个任务处理方式:

elixir虽然使用方便,但是有一个很大的问题是:如果你希望分开多个task,分别通过gulp/gulp watch来执行的话,是没有简单的方法的。

我现在使用的workaround是:另外创建一个gulpfile,在这个gulpfile中定义构建任务对应要做的工作,随后通过gulp --gulpfile yournewtaskfile来执行

browserify打包存在的缺陷:

在实际使用中发现,browserify打包只能处理简单的语法词法问题,比如缺少一个' , '号,import不存在的文件这样的问题,对于比如在components中定义了一个不存在的component类这类"runtime"的错误,它并不检查,甚至比如我们import了一个类,但是components数组引用中使用另外一个不存在的类名,这类问题在browserify打包过程中并不会发现,只有运行时在浏览器console终端中显示出来。

Eloquent relation/表结构映射

laravel提供了将数据表row映射为model的简单强大的机制,我们可以使用model来访问对应的数据集,同样地,laravel eloquent也支持模型之间的relation.这些relation包含以下几种:

one-to-one

one-to-many

在一对多这种模式中(比如user和task的关系:user hasMany(Task):  tasksCreatedByMe, tasksOwnedByMe;  Task belongsTo(User): createdBy, ownedBy

上述几对关系中:

User model:  tasksCreatedByMe .vs. Task model:  createdBy 

User model: tasksOwnedByMe .vs. Task model: ownedBy

都依赖于以下表结构: tasks表中必须分别有两个外键owner和creator分别引用users表中的id字段。

表格的结构需要在Task表中有两个字段: owner/creator

many-to-many

laravel find with

假设下面的场景: 需要查找返回一个user及其所有tasks,并且只能使用一次数据库查询,user和task之间有hasMany, belongsTo的relation:

$userinfo = User::with('tasks')->where('id','=',$user->id)->first(); //注意:first()返回的是User model, get()返回的是一个集合

 Eager loading with nested resource

有时,你希望一次性地获取资源对应的relation,并且可能希望嵌套的relation也能获取,比如,一本书Book属于一个author,一个author又有对应的contacts信息,你希望一次性获取一本书对应的作者以及作者对应的联系信息.可以通过以下方法一次性搞定(使用dot语法!!!):

$books = App\Book::with('author.contacts')->get();

Eager loading with nested resource and selected columns

有时,你希望一次性地获取资源对应的relation,并且可能希望嵌套的relation也能获取,并且限制两层relation对应需要选择的字段(减少网络传输,提高性能)比如,一本书Book属于一个author,一个author又有对应的contacts信息,你希望一次性获取一本书对应的作者(name,id字段)以及作者对应的联系信息(address,user_id字段).可以通过以下方法一次性搞定(使用dot语法!!!):

$books = App\Book::with(['author'=>function($q){
 $q->select(['id','name']);
}, 'author.contacts'=>function($q){
 $q->select(['address','user_id']; // 注意user_id字段是必选的哦,因为这是user和address表的外键!
})->get();

 再来一个例子:

$query->with([
    'child' => function ($q) {
        $q->with(['grandchild' => function ($q) {
            $q->where(‘someOtherCol’, ‘someOtherVal’); //constraint on grandchild
        }])
        ->where(’someCol', ’someVal’)->select(['childcol1','childcol2']); //constraint on child
    }
]);

 

表单验证方法1:custom form request for validation

对于form表单的验证时后端开发常见的技术要求,我们可以通过N种方案来实现这个要求,但是到底哪种可以作为最佳实践呢?随着laravel5.0引入了custom form request的feature,这个功能专门就是彻底解决这个问题的,可以说是laravel后端表单认证的最佳实践,并且你可以方便地指定谁有相关权限执行这个操作,以及对应的validation rule。

同时你也可以通过overwrite messages方法来自定义你希望展示的错误信息。

使用流程:

1. php artisan make:request YourCustomizedRequest;

2. 在该YourCustomizedRequest类中,定义authorize来指定对该request访问的授权规则

public function authorize()
{
    $commentId = $this->route('comment');

    return Comment::where('id', $commentId)
                  ->where('user_id', Auth::id())->exists();
}
public function messages()
{
    return [
        'title.required' => 'A title is required',
        'body.required'  => 'A message is required',
    ];
}

3.通过overwirte messages方法来自定义验证错误信息;

4.通过TypeHinting YourCustomizedRequest这个Request来调用这个request中定义的authorize策略

 

表单验证方法2:手工在控制器方法中创建validator执行自定义的validate,推荐

public function store(Request $request)
    {
        $validator = Validator::make($request->all(), [
            'title' => 'bail|required|unique:posts|max:255', //注意:bail关键字表示只要第一个验证失败后面就不验了,提高效率,具体rules可以参考:
//https://www.laravel.com/docs/5.2/validation#available-validation-rules
'body' => 'required', ]); //在这里我们可以通过after方法引入一个callback,做我们自己的额外处理 $validator->after(function($validator) { if ($this->somethingElseIsInvalid()) { $validator->errors()->add('field', 'Something is wrong with this field!'); } }); if ($validator->fails()) { return redirect('post/create') ->withErrors($validator, 'login') //注意这里login是可选的namespace参数, //这时,使用{{ $errors->login->first('email') }}的命名空间方式引用error bag ->withInput(); } // Store the blog post... }

使用Laravel自带的控制器trait方法实现简单表单数据验证:

$this->validate($request, [
    'title' => 'required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

laravel authenticate and authorize相关知识点

authenticate: 就是告诉系统你是谁,这个往往是通过你登录系统,laravel在session中保存User来实现的;

authorize: 就是系统告诉你你有做什么事情的权限,比如你可以查看敏感数据,可以删除修改部分资源

middleware: 就是一个http request到达你的业务逻辑(控制器方法)前必须做的必要检查,只有这些middleware层层通过后,才能最后调用你的控制器方法逻辑;

实现一个应用授权谨慎让应该有对应权限的人做对应权限的事是laravel应用的诉求。如何具体实现不是一个简单的事情,需要认真思考。我梳理总结以下原则和策略:

1. 凡是通用的适用于大片entry point的监察逻辑都通过应用对应的middleware到路由集(Route::group)上去来实现:

比如,我们所有/admin/xx的路由都必须有管理员权限的角色才能够访问,再比如只有vip登录用户才能访问受保护的资源,最好的方法就是在受保护的路由集中使用middleware来控制

Route::group(['prefix'=>'admin','middleware'=> ['role:admin']],function(){
// 应用一个role:admin这个middleware到/admin/xxx的路由集上
    /* admin/roles */
    Route::get('/roles',['as'=>'adminroles','uses'=>'Admin\AdminRolesController@index']);
    。。。
});

2.对于在一个控制器中的部分方法需要保护,比如如果你希望创建修改删除一个task,那么可能涉及到几个action和对应的页面: create, store, update,delete,能够访问这些页面的前提是你必须登录后才能执行。那么这时比较合适的方式可能是在控制器构造函数中引入middleware:

public function __construct()
    {
// 我们在Controller构造函数中应用定制的mustloggedin middleware,指定只对create/store/update/delete做保护,其他的action全部放行
        $this->middleware('mustloggedin',['only'=>['create','store','update','delete']]);
    }
// mustloggedin middleware定义:
class MustLoggedIn
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->guest()) {
            if ($request->ajax() || $request->wantsJson()) {
                return response('Unauthorized.', 401);
            } else {
                return redirect()->guest('/')->withErrors('您必须登录后才能访问页面: '.$request->fullUrl());
            }
        }
        return $next($request);
    }
}

3. 对于要求更具体的一些action,比如update一个task的内容,你可能要求必须是task owner才能有权限编辑更改task的内容,这时,比较合适的方法就是使用custom request来实现。具体可以参考上面custom form request的部分

Middleware parameters

middleware很好用,但是很多人可能不清楚如何向middleware传入参数及使用方法:

1. 在路由或者控制器中引入带参数的middleware(使用);

public function __construct()
    {
        $this->middleware('role:admin',['only'=>['create','store','update']]);
    }

 

2. 在kernel.php的对应routeMiddleware  section中添加role middleware的声明

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        // entrust middleware
        'role' => \Zizaco\Entrust\Middleware\EntrustRole::class,
        'permission' => \Zizaco\Entrust\Middleware\EntrustPermission::class,
        'ability' => \Zizaco\Entrust\Middleware\EntrustAbility::class,
        // my middleware
        'mustloggedin' => \App\Http\Middleware\MustLoggedIn::class,

    ];

 

3.在role middleware的handle函数中,你传入的参数就以第三个参数来访问了(定义)

public function handle($request, Closure $next, $roles)
    {//这里$roles就是在middle引用的时候传入的参数
        if ($this->auth->guest() || !$request->user()->hasRole(explode('|', $roles))) {
            abort(403,"您不具有 ".$roles." 的角色!"." 无权访问 ".$request->fullUrl());
        }

        return $next($request);
    }

 什么是laravel policy?

policy提供了一种定义对一个action访问控制的机制,policy class中的所有方法都对应着controller的对应action.

比如PostPolicy的一个update方法可能定义了谁有权限update一个post,而在PostController的update方法中,我们使用if(Gate::denies('update',$post)){abort('403');}

PHP get_class($object) 返回该object对应的class名称

laravel balde和对应plain PHP代码的映射

Plain PHP views (.php)Blade views (.blade.php)
Outputting data in PHPOutputting data using Blade
<?php echo(date('Y')); ?>{{ date('Y') }}
Looping through data with PHPLooping through data with Blade
<?php foreach($users as $user){ ?>@foreach($users as $user)
<p><p>
<?php echo($userelname); ?><br>{{ $userelname }}<br>
<?php echo($usereladdress); ?>{{ $usereladdress }}
</p></p>
<?php } ?>@endforeach
Using conditional statements in PHPUsing conditional statements in Blade
<?php if($category == 'blog') { ?>@if($category == 'blog')
......
<?php } else { ?>@else
......
<?php } ?>@endif

laravel /login路由对于已经登陆用户是如何保护的?

对于受控资源的访问控制,比如必须登陆之后才允许发表评论,这样的场景我们应该会司空见惯。但是如果已经登录了系统,如果再次访问/login路由的话,系统自动重定向到主页,这个功能到底是怎么工作的呢?

laravel自带了几个middleware,其中一个就是guest middleware,它就是实现这个功能的。通过php artisan route:list我们可以列出对应的路由中包含了guest middleware的保护引用。

GET|HEAD  | login                                |                     | App\Http\Controllers\Auth\AuthController@showLoginForm                 | web,guest
//该guest middleware的定义为:
public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/');
        }

        return $next($request);
    }

 

我们可能会用auth

POLYMORPHIC RELATIONSHIPS

这个多态关系是个什么鬼?简单说就是一个model可以belongsTo多个其他的model ,貌似比较傻,可以有很多其他的解决方案的

http://www.richardbagshaw.co.uk/laravel-user-types-and-polymorphic-relationships/

laravel elixir

elixir是laravel前端构建工具集,它包装了gulp,所有gulp task都由elixir来包装管理。其配置文件在config.js中,可以在gulpfile.js文件中通过elixir.config.*来overwrite,

默认的配置项及值有:

generic:
    assetsPath: 'resources/assets',
    publicPath: 'public',
    appPath: 'app',
    viewPath: 'resources/views',
css:
        folder: 'css',
        outputFolder: 'css',
        autoprefix: {
            enabled: true,

            // https://www.npmjs.com/package/gulp-autoprefixer#api
            options:  {
                browsers: ['last 2 versions'],
                cascade: false
            }
        }
less: {
            folder: 'less'}
js: 
        folder: 'js',
        outputFolder: 'js',
        babel: {
            // https://www.npmjs.com/package/gulp-babel#babel-options
            options: {
                presets: ['es2015', 'react']
            }
        }

 

laravel pagination with query parameter

使用laravel, 分页pagination实在再简单不过了,但是有时我们需要同时支持search查询的功能,这时分页点击时,我们也还是希望看到的是查询内容里面的分页。很简单:

public function apidata(Request $request)
    {
        $search = $request->query('title');
        if($search){
            // appends方法自动将query string加到pagination的url中
            // search方法是scoped query,自动返回like $search string的结果集
            // paginate则以分页方式返回结果集
            return Skill::whereLevel(1)->with(['cates.parents','children'])->search($search)->paginate(5)->appends($request->input());
        };
}

orderBy relation eager loading

当eager loading一个relation时,可以方便地对结果集排序,比如Node model其children(是递归的)就可以用updated_at来排序

class Node extend eloquent{    
public function children($recursive = false){
        return $this->child()->with('children')->orderBy('updated_at','DESC');
    }
}

constrain for eager loading: 只选中部分字段():

return Classroom::with(['teachers'=>function($query){
            $query->select('teacher_id', 'name','realname');  //teacher_id字段必选选中,否则不work
        }])->paginate(10);

 

laravel大表分区共用一个model

如果一张表格非常大,你可能希望将表格根据时间分为小表,比如orders_201501,orders_201506等等,但是希望使用一个Order model,底层的table则需要根据时间来选择。

http://stackoverflow.com/questions/26757452/laravel-eloquent-accessing-properties-and-dynamic-table-names

laravel whereRAW调用sql函数

$namelength = iconv_strlen($prefix)+4;
$existusers = User::where('name','like','%' . $prefix . '%')->whereRaw('LENGTH(name) = ?', array($namelength))->orderBy('name','desc')->pluck('name')->toArray();

上面的例子检索所有name字段长度为$prefix+4,并且name包含$prefix字符串的记录

 laravel eager loading relation constrain:限定relation的条目数

参考以下: https://softonsofa.com/tweaking-eloquent-relations-how-to-get-n-related-models-per-parent/

定制custom relation vs hydrate

laravel eloquent虽然非常强大基本上都能够满足所有数据库关系,但是仍然有很多时候无法简单地通过一个belongsTo, belongsToMany, hasOne这种简单的关系来实现,有两个可能的思路:

1. 定制你自己的custom relationship方法,随心所欲满足你的定制化要求,但是需要对laravel model relation类有比较清晰的理解,典型引用案例就是eager loading with limits(因为你无法对eager loading的relation限制条目!)

"

I needed a complex sql to resolve the relationship. What I ended up doing is extending the base class for relations and creating my own, right now in laravel 5.2 that is Illuminate\Database\Eloquent\Relations\Relation. It's quite self-explanatory implementing the abstract methods, but if you need any examples you can see the implementations of HasMany, BelongsTo, etc. all the relations that come with laravel out of the box.

"

https://laravel.io/forum/06-15-2015-custom-relationship-queries

https://github.com/johnnyfreeman/laravel-custom-relation

https://laracasts.com/discuss/channels/eloquent/create-a-custom-relationship-method

2. 直接使用raw sql来获取到你的model,随后hydrate到你的model中去,这个方法不支持eager loading

 "

In another note, you can always create your custom queries or getting information wherever you want and create a collection of laravel models using the static ::hydrate() and ::hydrateRaw() methods in your model classes.

"

详细的hydrate调用案例:

http://stackoverflow.com/questions/22312586/laravel-select-from-derived-table#answer-25069239

如何在laravel middleware中访问request的parameter?

我们在开发自己的授权系统时,往往会用到强大的middleware功能,在middleware中做相应的检查看看用户是否有对资源的访问权限,这个资源本身往往由route+parameter来共同决定。

这些个信息可以通过以下代码来访问:

public function handle($request, Closure $next)
    {
        $parameters = $request->route()->parameters();
        $routename = $request->route()->getName();
            return $next($request);
    }

https://scotch.io/tutorials/get-laravel-route-parameters-in-middleware

laravel如何获取$_GET大数组中的query url variable?

$title = Input::get('title'); // 这段代码等同于下面
        try {
            $title = $_GET['title']; // get query string title value xxxx ?title=xxxx
        }catch (\Exception $e){}

如何eager loading relation with count?

$posts = Post::leftJoin('comments', 'comments.post_id', '=', 'posts.id')
  ->select('posts.*', 'count(*) as commentsCount')
  ->groupBy('posts.id')
  ->get();
 
$posts = Post::leftJoin('comments', 'comments.post_id', '=', 'posts.id')
  ->select('posts.*', 'count(*) as commentsCount')
  ->groupBy('posts.id')
  ->get();

https://softonsofa.com/tweaking-eloquent-relations-how-to-get-hasmany-relation-count-efficiently/

PHP 类的Magic methods/functions

__counstruct

构造函数,每一个类在初始化时自动调用,你可以执行依赖的注入,参数初始化等

__set($name,$value)

当试图给一个不能访问的属性来赋值时被自动调用,比如如果试图执行$obj->privatePropery = $value这个代码时,由于privateProperty是私有的,因此无法通过这种$obj->property来访问,这时这个__set函数就将被执行,并且将$name的property赋值为$valuclass xx {

private $privatepropery = '';
  private $username;  // 可以被读以及写(通过__set)
  private $password;  // 可以被写,但是不可读(通过__get)
  private $age;  
  private $accessibles = ['username'];
  private $fillables = ['username','password'];
  public function __get($name){
     if(!in_array($name,$this->accesibles)){
          // 如果访问的属性不在$accessibles数组定义的白名单中,则不允许访问
         return Null;
      }
     if(isset($name)){
        return $this->$name; // 首先判断该类中是否有这个属性,有则赋值,没有则忽略
     }
  }
public function __set($name,$value){
     if(!in_array($name,$this->fillables)){
          // 如果访问的属性不在$fillable数组定义的白名单中,则不允许赋值
         return false;
      }
     if(isset($name)){
        $this->$name=$value; // 首先判断该类中是否有这个属性,有则赋值,没有则忽略
     }
  }
} $obj = new xx; $obj->username //可以访问 $obj->password // 返回null $obj->nonexist //返回null

 

__get($name)

当试图访问一个私有属性时,则该magic方法被调用

__toString

当对象被cast to string时自动调用,比如echo $obj这时由于$obj不是string,那么就会自动调用$obj的__toString方法

http://php.net/manual/en/language.oop5.magic.php

__construct()__destruct()__call()__callStatic()__get()__set()__isset()__unset(),__sleep()__wakeup()__toString()__invoke()__set_state()__clone() and __debugInfo()

__call($name,$arguments) 

is triggered when invoking inaccessible methods in an object context.

如果在对象调用的context中,被调用的方法不存在,则自动调用这个__call方法。 $obj->nonexist();

__callStatic($name,$arguments)

is triggered when invoking inaccessible methods in an object context.

如果在static环境中调用不存在的方法,则自动调用这个__call方法。 ClassName::nonexist();

 

<?php
class MethodTest
{
    public function __call($name, $arguments)
    {
        // Note: value of $name is case sensitive.
        echo "Calling object method '$name' "
             . implode(', ', $arguments). "\n";
    }

    /**  As of PHP 5.3.0  */
    public static function __callStatic($name, $arguments)
    {
        // Note: value of $name is case sensitive.
        echo "Calling static method '$name' "
             . implode(', ', $arguments). "\n";
    }
}

$obj = new MethodTest;
$obj->runTest('in object context');

MethodTest::runTest('in static context');  // As of PHP 5.3.0
?>

上面的__call和__callStatic在laravel中非常常用,比如facade都定义了__callStatic,以静态方式写代码,但是实际上最终用这个__callStatic首先解析出obj然后调用到真正的实体对象的方法

PHP子类重载父类并通过parent::method调用父类的方法

class User{
   public function login(){
      var_dump('user logged in...');
   }
}
class Administrator extends User{
   public function login(){
      parent::login(); //调用子类的login方法
      var_dump('user logged in as administrator!');
   }
}

(new Administrator)->login();
// "user logged in..."
// "user logged in as administrator!"

如何判断session是否timeout?

在laravel应用中,session的默认为120,一旦超过这个时间,服务端的session就将timeout被删除,而这时如果有ajax请求就会莫名其妙地出错,但是用户却可能不知情。最好是能够主动在后端判断session是否timeout,如果已经过期则回复对应的消息,提示用户重新登录:

http://stackoverflow.com/questions/14688853/check-for-session-timeout-in-laravel

if ((time() - Session::activity()) > (Config::get('session.lifetime') * 60))
{
   // Session expired
}

可以做成一个middleware,在每个request处理时触发

如何使能redis session driver?

通过配置.env文件中的

SESSION_DRIVER=redis

如何获取http://xxx.com/yy?q=zz#/urlafterhashbound/mm整个url?

我们知道url中的#后面的内容是为浏览器客户端来使用的,永远不会送往server端,那么如果服务器端希望得到这个信息,又该如何处理呢?一个可行的方案是在url中将#后面的内容转换为querystring,这样后端就能够得到这个信息加上fullurl()函数就能拼凑出整个url

 

Laravel 使用 env 读取环境变量为 null问题

在使用laravel env()函数来读取.env文件中的配置信息过程中,偶尔你会发现获取不到。后来再通过执行php artisan config:cache命令来模拟问题出现场景,发现该命令执行后,在tinker中执行env就永远无法获取。看看下面的laravel代码Illuminate/Foundation/Bootstrap/DetectEnvironment.php line 18

public function bootstrap(Application $app)
{
    if (! $app->configurationIsCached()) {
        $this->checkForSpecificEnvironmentFile($app);

        try {
            (new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
        } catch (InvalidPathException $e) {
            //
        }
    }
}

上述这段代码说明了如果存在缓存配置文件,就不会去设置环境变量了,配置都读缓存配置文件,而不会再读环境变量了。

因此,在配置文件即 app/config 目录下的其他地方,读取配置不要使用 env 函数去读环境变量,这样你一旦执行 php artisan config:cache 之后,env 函数就不起作用了。所有要用到的环境变量,在 app/config 目录的配置文件中通过 env 读取,其他地方要用到环境变量的都统一读配置文件而不是使用 env 函数读取。

whereIn 排序

很多时候,我们会通过whereIn([1,5,8])这种方式来查找对应数据库记录,但是mysql会自动按照自然升序排列结果集,而这可能不是我们想要的。

解决办法是mysql的order by field来实现,对应laravel就是orderByRaw()

select * from `persons` where `id` in (1437, 1436, 1435, 1434, 1429) order by FIELD(id, 1437,1436,1435,1434,1429)
$implode(',',$idsarray);
Person::whereIn('id',$idsarray)->orderByRaw(DB::raw("FIELD(id, ${idsorderstr})"))->get();

如何将PHP array转换为sql查询中的in字段

$arraydata = [1,5];
$idquery = join("','",$arraydata);
$ids = \DB::select("select cf.id as pid from cates cf join cates cs on cf.id = cs.root where cs.id in ('$idquery')  order by cf.id ");

 

DB::raw使用PHP变量以及subquery的使用方法

return \DB::table(\DB::raw("
            (select * from (select *,SUM(CASE isright

        WHEN 1 THEN 1

        ELSE 0

    END) AS right_count  from useranswers where class_id = '$classid%' and user_id='$uid%' group by user_id) as mine
    union
select * from (select *,SUM(CASE isright

        WHEN 1 THEN 1

        ELSE 0

    END) AS right_count  from useranswers where class_id = '$classid%' group by user_id order by right_count desc limit 2) as top2) as answerrights
            "))->get();

 

如何访问Content-Type: application/x-www-form-urlencoded的post body内容?

有时候需要访问post body的内容,虽然$request->get('keyname')绝大多数时间是可以满足需求的,但是还是有很多时候我们希望能够完全得到post body的内容并且按照自己的方式来处理。方法是:

$bodyContent = $request->getContent();
$decodedBody = json_decode($bodyContent);
// 下面就可以直接对decodedBody对象操作了

 

MySQL数据库设计nullable考虑

建议不要使用nullable修饰,因为会影响性能并且占用空间,最好使用laravel的默认NOT NULL

http://blog.csdn.net/dsislander/article/details/8573666

如何在eager loading时对relationship做orderby操作?

我们知道在laravel中返回结果集时,只能对最外层的model做orderby操作,如何在eager loading一个关系relation时同时以该对该relation做排序呢?

答案是:

在定义关系函数时,直接在query builder中添加order by 字段

public function comments()
{
    return $this->hasMany('Comment')->orderBy('column');
}

如何在返回model时自动附加上另外一个属性? appends+getterXXXAttribute

在某些场景下,当我们返回model时希望自动加上一个额外的属性,比如返回post model时希望自动加上其user信息,返回班级classroom时,自动返回其已经布置的作业数目等,这时就需要使用laravel model的appends功能了:

class classroom extends Model{
  public $table = 'classrooms';
  protected $appends = ['homework_count'];
  /**
     * @return mixed accessor for homeworkCount attribute
     */
    public function getHomeworkCountAttribute() {
        $homeworkscount = $this->homework()->count('homeworks.id');
        return $homeworkscount;
    }
}

如何简化DB::table('xxx')->get()返回的obj数组为纯粹values以便减显著少传输字节数?

 在DB::table('xxx')->get()返回的对象数组中,如果数据量很大,字段数也比较多的话,一般情况下字段长度都比较长,字段的value本身却很短,因此,有效载荷是很小的,浪费传输带宽降低网站响应速度。一个有效的优化方法就是直接去除这些对象数组中的key值传输,只传value。具体方法如下:

$draftdata = DB::table(DB::raw("xxxSQL"))->get(['id','title','someotherfield']);
$temp = array_map(function($v){
                return array_values(get_object_vars($v));
            },$draftdata);
// 经过上面的array_values(只返回array的value而忽略对应的key)和get_object_vars操作(将对象修改为array)变换后就返回了已经简化的数据
// [ [1,'这是title','第三个字段'],[2,'这是第二个title','第三个字段的value']]
            return ($temp);

 laravel图片验证码package集成 mews/captcha

下面是validator的扩展代码,这样就可以在controller中使用captcha这个rule了,其底层逻辑由captcha_check来实现

        // Validator extensions
        $this->app['validator']->extend('captcha', function($attribute, $value, $parameters)
        {
            return captcha_check($value);
        });

 如何在NotFoundHttpException中访问session?

由于laravel startsession middleware本身默认情况下只有在顺利通过了route检查后才能启动,这样就会导致在Http not found exception中无法访问到session,如果你确实需要在任何情况下都能访问session,应该怎么办?方法很简单,将Session\Middleware\StartSession::class,这个middleware放到kernel.php的$middleware定义中

    protected $middleware = [
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Session\Middleware\StartSession::class,
    ];

composer update one vendor(或者希望安装一个composer的特定版本)

有的时候我们只希望更新composer.json中的某一个vendor的package,而其他的package都保持不变,有什么办法呢?

1. 首先在composer.json中增加你希望安装的package以及版本要求;

2. 执行composer update vendor/package的命令就好了!

如何动态地隐藏model及relation的字段

$notification->setHidden(['segmentednotification']);

 Carbon和PHP date之间的各种转换

carbon本身是一个非常好用的PHP date package,但是有的时候我们还希望用到php原生的函数功能,比如format成微信开发需要的20180120112305的字符串格式

$n = new \Carbon\Carbon();
$ts =  $n->getTimestamp();
return date('YmdHis',$ts);  // 20180104161547
$u = User::orderBy('created_at','desc')->first();
$ts = $u->created_at; // 被laravel自动cast为carbon object
return date('YmdHis',$ts->getTimestamp());  // 20180104161547

 

微信公众号开发相关的几个PHP典型函数实现

public function MakeSign()
    {
        //签名步骤一:按字典序排序参数
        ksort($this->values);
        $string = $this->ToUrlParams();
        //签名步骤二:在string后加入KEY
        $string = $string . "&key=".WxPayConfig::KEY;
        //签名步骤三:MD5加密
        $string = md5($string);
        //签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }
public function SetSign()
    {
        $sign = $this->MakeSign();
        $this->values['sign'] = $sign;
        return $sign;
    }
    
public static function getNonceStr($length = 32) 
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";  
        $str ="";
        for ( $i = 0; $i < $length; $i++ )  {  
            $str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);  
        } 
        return $str;
    }

public function ToXml()
    {
        if(!is_array($this->values) 
            || count($this->values) <= 0)
        {
            throw new WxPayException("数组数据异常!");
        }
        
        $xml = "<xml>";
        foreach ($this->values as $key=>$val)
        {
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml; 
    }
    
    /**
     * 将xml转为array
     * @param string $xml
     * @throws WxPayException
     */
    public function FromXml($xml)
    {    
        if(!$xml){
            throw new WxPayException("xml数据异常!");
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
        return $this->values;
    }

使用call_user_func_array调用动态传入的function或者$obj->method并使用到对应参数

<?php
function foobar($arg, $arg2) {
    echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
    function bar($arg, $arg2) {
        echo __METHOD__, " got $arg and $arg2\n";
    }
}


// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));

// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
?>

微信公众平台开发中的网页授权域只能设置一个如何满足多个域名的网页授权需求?

http://www.cnblogs.com/lyzg/p/6159617.html

laravel路由注意不要有 trailing slash / !!!否则会额外多一个redirect,这个可能是由.htaccess来强制跳转的

如何将laravel object/array直接通过html传给javascript?

<script>
var user = {!! json_encode(userobj()) !!} // userobj是laravel拉取数据库形成的PHP对象或数组
</script>

 上面的代码将使得PHP的输出不经过htmlentities这个php函数来过滤转换html相关的内容,直接原封不动的输出给js

 laravel session flash data如何在连续redirect时保持?

有时候存在这样的场景:我们在前一个request处理时使用withErrors('error information')在laravel session store中flash了一个errors,本来在下一个request中我们是可以访问到这个error的,但是可能在middleware中又强制做了一个redirect,而这时是不再带errors的,这时由于laravel的session store机制会将flash.old清空,因此在最终的request处理函数中就无法获取到该error,怎么办?一个可行的策略是:

 $request->session()->reflash(); // 将flash data保留到下一次访问

 Composer错误处理 Please create a github oauth token

$ composer create-project --prefer-dist laravel/laravel lara56

Installing laravel/laravel (v5.4.30)
  - Installing laravel/laravel (v5.4.30): Downloading (connecting...)
Could not fetch https://api.github.com/repos/laravel/laravel/zipball/098b8a48830c0e4e6ba6540979bf2459c8a6a49e, please create a GitHub OAuth token to go over the API rate limit
Head to https://github.com/settings/tokens/new?scopes=repo&description=Composer+on+USER-20151001BU+2018-03-01+1407
to retrieve a token. It will be stored in "C:/Users/Administrator/AppData/Roaming/Composer/auth.json" for future use by Composer.
Token (hidden):
No token given, aborting.
You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"
    Failed to download laravel/laravel from dist: Could not authenticate against github.com
    Now trying to download from source
  - Installing laravel/laravel (v5.4.30):

解决办法:

1. 通过:https://github.com/settings/tokens创建一个token

2. composer config --global github-oauth.github.com 23bc35a888235f66032ef68c7a8023b7b28e0f6

如何使用composer安装laravel的一个特定版本?

1. 在packagist中查找对应framework的版本: https://packagist.org/packages/laravel/framework

2. composer create-project laravel/laravel lara560 --prefer-dist 5.6.0 

但是要注意的是laravel/laravel可能和laravel/framework的版本并不会很快同步的哦, 比如laravel/framekwork在v5.6.7版本时,而laravel/laravel却可能只在v5.6.0上

laravel Mix build 无法使用sourcemap的问题

解决办法, 在webpack.mix.js中,使用以下代码,之后再调用你自己的mix.js.sass等任务

mix.webpackConfig({
    devtool: "inline-source-map"
});

RuntimeException: No supported encrypter found. The cipher and / or key length are invalid解决办法

php artisan config:cache

如何在laravel中增加自己的config并使用.env文件抛入对应敏感配置数据?

1. 在config目录下增加一个yourcfg.php

2.该php文件中

return [
 'myconfigkey1' => env('MYCONFIGKEY1',DEFAULTVALUE
]

3. 执行

php artisan config:cache

上面这条命令将使得所有config cache起来,这时你如果修改你的env文件你会发现并不会立即生效,那么要么再次执行上述config:cache命令,要么执行以下命令:

php artisan config:clear以便清除掉config的cache内容,但是这样做会有个性能的损失"

wherePivot, wherePivotIn针对多对多的pivot表来查询:

    public function category()
    {
        return $this->belongsToMany(Category::class)->wherePivotIn('id', [1]);
    }

model relationship with itself: 对自己建立关系

https://heera.it/laravel-model-relationship

Composer国内加速

和npm类似,国外网站从国内访问很多不稳定,速度慢。感谢又拍云,感谢活雷锋。免费为中国开发者搭建并运营着PHP composer的国内镜像,使用方法如下:

composer config -g repo.packagist composer https://packagist.phpcomposer.com

从此网络速度会大大提高。

https://pkg.phpcomposer.com/

vue-cli-serve来实现vue前端开发时使用laravel输出的view实现前后端配合开发

https://github.com/yyx990803/laravel-vue-cli-3

转载于:https://www.cnblogs.com/kidsitcn/p/5370369.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值