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 PHP Outputting data using Blade
<?php echo(date('Y')); ?> {{ date('Y') }}
Looping through data with PHP Looping 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 PHP Using 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM