2018年9月22日 星期六

laravel 常見問題


重複使用query builder

https://stackoverflow.com/questions/30235551/laravel-query-builder-re-use-query-with-amended-where-statement
使用 clone
$oQuery = Model::where('is_test', '=', '0')->groupBy('name');

$oQuery1 = clone $oQuery;
$oQuery1Results = $oQuery1->where('content','like','%2%')->get();
$oQuery2 = clone $oQuery;
$oQuery2Results = $oQuery2->where('content','like','%0%')->get();


安裝Start Bootstrap Admin 2( SB Admin 2 )

https://startbootstrap.com/template-overviews/sb-admin-2/
Step 1. 安裝新的Laravel
$ composer create-project --prefer-dist laravel/laravel blog
Step 2. 下載 SB Admin 
Download SB Admin Theme
Point 1. 新建 public/theme/ 目錄
Point 2. 將SB Admin解壓縮出來的data, dist, vendor目錄放到theme目錄
Step 3. 生成路由
routes/web.php 新增
Route::get('my-home', 'HomeController@myHome');
Route::get('my-users', 'HomeController@myUsers');
Step 4. 新增控制器
app/Http/Controllers/HomeController.php
(內容見上面教程)
Step 5. 設定theme 的blade檔案
新建 resources/views/theme/ 目錄,並生成三個文件
resources/views/theme/default.blade.php
resources/views/theme/header.blade.php
resources/views/theme/sidebar.blade.php
(內容見上面教程)
ps . <script src="{!! asset('theme/data/morris-data.js') !!}"></script> 必須有畫圖表再加載,否則會報js錯誤
Step 6. 使用theme
新建下面兩個檔案
resources/views/myHome.blade.php
resources/views/myUsers.blade.php
(內容見上面教程)

生成.env的APP_KEY

$ php artisan key:generate

名詞

Accessor - 訪問器 => getFirstNameAttribute()
Mutator - 修改器 => setFirstNameAttribute()
guards - 看守器
providers - 提供器

用戶認證(Authentication)

https://laravel-china.org/docs/laravel/5.7/authentication/2269
https://laravel.com/docs/5.7/authentication
快速生成身分驗證所需的路由和視圖
$ php artisan make:auth
打開註冊頁
http://laravel.localhost/login
點Register註冊一個新帳號,users.password 將會以Hash儲存,然後就能以此新帳號登入

使用username登入
https://laracasts.com/discuss/channels/laravel/how-to-use-authentication-using-username-instead-of-email
app/Http/Controllers/Auth/LoginController.php 新增
public function username(){
    return 'username';
}
resources/views/auth/login.blade.php 前端也要做處理,以免被input.type=email擋住
使用email或username登入
https://stackoverflow.com/questions/42708942/allow-login-using-username-or-email-in-laravel-5-4
app/Http/Controllers/Auth/LoginController.php
public function username() {
   $login = request()->input('login');
   $field = filter_var($login, FILTER_VALIDATE_EMAIL) ? 'email' : 'username';
   request()->merge([$field => $login]);
   return $field;
}
讓Ardent能夠使用Authenticatable
https://stackoverflow.com/questions/46419933/laravel-ardent-with-authenticatable-user-model/46421896#46421896
建議參考 Illuminate/Foundation/Auth/User 的User類別的實作方式到你的model上,然後繼承Ardent

使用Tinker

https://www.sunzhongwei.com/using-laravel-tinker-for-laravel-admin-add-background-administrator
Tinker(修補匠) - 與你的laravel應用程式互動(Interact with your application)
$ php artisan tinker
Psy Shell v0.9.9 (PHP 7.1.7 — cli) by Justin Hileman
>>> $user = new App\Models\User();
=> App\Models\User {#3116}
>>> $user->password = Hash::make('1234qwer');
=> "$2y$10$KRgwZyc8j.3rgE0kBMW/8usLnsc3DGuZ6GtT3JiI1zgsBlxstq8RG"
>>> $user->email = 'tinker@example.com';
=> "tinker@example.com"
>>> $user->save();
=> true
可以直接在命令行執行laravel

不同網站同一個專案
https://stackoverflow.com/questions/40604788/multiple-websites-with-one-laravel-installation
Route::group(['domain' => 'example.com')], function () {
    // All routes for the first domain go here.
}

多個域名走同一個路由
https://stackoverflow.com/questions/18603330/can-i-group-multiple-domains-in-a-routing-group-in-laravel
$appRoutes = function() {
    Route::get('/',function(){
        ...
    }); 
};

Route::group(array('domain' => 'app.example.com'), $appRoutes);
Route::group(array('domain' => 'dev.app.example.com'), $appRoutes);

子域名路由不作用
https://stackoverflow.com/questions/41703192/laravel-subdomain-routing-is-not-working
因為路由有先後順序(先來先服務),所以子域名同路徑的路由必須寫在前面
Route::group(['domain' => 'admin.localhost'], function () {
    Route::get('/', function () {
        return "This will respond to requests for 'admin.localhost/'";
    });
});

Route::get('/', function () {
    return "This will respond to all other '/' requests.";
});
下面例子子域名路由不作用
Route::get('/', function () {
    return "This will respond to all '/' requests before the route group gets processed.";
});

Route::group(['domain' => 'admin.localhost'], function () {
    Route::get('/', function () {
        return "This will never be called";
    });
});

在路由中動態加載域名
https://stackoverflow.com/questions/34501476/laravel-5-routes-group-domains-from-rows-in-database
這邊可以使用Eloquent
$domains = \App\Domain::where('status', '1')->get()->pluck('email');

foreach ($domains as $domain) {
    Route::group(['domain' => $domain], function () {
        Route::get('/{linkName}', 'RetargetController@retarget');
        Route::get('/', function () {
            abort(404);
        });
    });
}

php artisan make:auth 與Ardent的衝突
laravel 官方驗證 php artisan make:auth後會生成 Http/Controllers/Auth/RegisterController.php 其中的創建用戶方法create()是這麼寫的
return User::create([
  ...
  'password' => Hash::make($data['password']),  => $data['password']
如果你的User已經繼承了Ardent,而且 autoHashPasswordAttributes 了 password這個欄位,password將會被Hash兩次造成登入不了的bug... 這時候需在 RegisterController::create()中取消password字段的Hash













2018年9月4日 星期二

PHP中的自動加載

目的:
new一個class時不需要寫require。 PHP 5.2 新增自動加載的魔術方法__autoload($class_name)

一、__autoload()的使用

__autoload.php
$db = new DB();

Height::test();  //也是支持静态方法直接调用的

function __autoload($className)
{
    require $className . '.php';
}
DB.php(同個目錄下)
class DB
{
    public function __construct()
    {
        echo 'Hello DB';
    }
}
Height.php(同個目錄下)
class Height
{
    public function __construct()
    {
        echo 'Hello Height';
    }

    public static function test(){
     echo 'Hello Height test';
    }
}

二、spl_autoload_register 使用

__autoload()不允許加載不同路徑的文件,因為一個項目中只能有一個__autoload()。因此spl_autoload_register()誕生。當我們new一個找不到的class時,PHP會自動調用spl_autoload_register註冊的函數
class autoloading{
    //必须是静态方法,不然报错
    public static function load($className)
    {
        require $className . '.php';
    }
}
//2种方法都可以
spl_autoload_register(array('autoloading','load')); 
// spl_autoload_register('autoloading::load');
$db = new DB(); //会自动找到

三、多个spl_autoload_register的使用

function load1($className)
{
    echo 1;
    if (is_file($className . '.php')) {
        require $className . '.php';
    }
}
function load2($className)
{
    echo 2;
    if (is_file('./app/' . $className . '.php')) {
        require './app/' . $className . '.php';
    }
}
function __autoload($className)
{
    echo 3;
    if (is_file('./lib/' . $className . '.php')) {
        require './lib/' . $className . '.php';
    }
}
//注册了3个
spl_autoload_register('load1');
spl_autoload_register('load2');
spl_autoload_register('__autoload');
$db = new DB(); //DB就在本目录下
$info = new LibInfo(); //LibInfo 在/lib/LibInfo.php

var_dump(spl_autoload_functions());

輸出:
1
Hello DB
1
2
3
Hello LibInfo
array (size=3)
  0 => string 'load1' (length=5)
  1 => string 'load2' (length=5)
  2 => string '__autoload' (length=10)

使用spl_autoload_functions() 來查一共註冊了多少個自動加載
spl_autoload_register()使用時,__autoload()會無效,必須重新註冊進來才有效

四、spl_autoload_register自動加載+namespace命名空間

根據 PSR-0 的規範,使用namespace就能找到詳細的路徑,從而找到class文件
spl_autoload_register_namespace.php
//定义当前的目录绝对路径
define('DIR', dirname(__FILE__));
//加载这个文件
require DIR . '/loading.php';
//采用`命名空间`的方式注册。php 5.3 加入的
//也必须是得是static静态方法调用,然后就像加载namespace的方式调用,注意:不能使用use
spl_autoload_register("\\AutoLoading\\loading::autoload"); 

$oMySQL = new Libary\Db\MySQL();
$oMySQL->bear();

// 调用2个namespace类
//定位到Libary目录下的Name.php 
Libary\Name::test();
//定位到App目录下Android目录下的Name.php
App\Android\Name::test();

loading.php
namespace AutoLoading;
class loading {
    public static function autoload($className) {
        //根据PSR-O的第4点 把 \ 转换层(目录风格符) DIRECTORY_SEPARATOR ,
        //便于兼容Linux文件找。Windows 下(/ 和 \)是通用的
        //由于namspace 很规格,所以直接很快就能找到
        $fileName = str_replace('\\', DIRECTORY_SEPARATOR, DIR . '\\' . $className) . '.php';
        if (is_file($fileName)) {
            require $fileName;
        } else {
            echo $fileName . ' is not exist';die;
        }
    }
}

Libary/Db/MySQL.php
namespace Libary\Db;
use Libary\Name; // use放這
class MySQL
{
    public function __construct()
    {
        echo __NAMESPACE__ . "";
    }
    public static function test()
    {
        echo  __NAMESPACE__ . ' static function test ';
    }
    public function bear(){
        Name::getInstance(); // 呼叫 Libary/Name.php 的 static function getInstance()
    }
}

Libary/Name.php
namespace Libary;
use Libary\Db\MySQL; // use放在這
class Name
{
    public function __construct()
    {
        echo __NAMESPACE__ . "";
    }
    public static function test()
    {
        // $oMySQL = new Libary\Db\MySQL();  // D:\bear\www\test\autoload\Libary\Libary\Db\MySQL.php is not exist
        // $oMySQL = new Db\MySQL(); // OK
        // use Libary\Db\MySQL; // Parse error: syntax error, unexpected 'use' (T_USE)
        $oMySQL = new MySQL(); // 打印第二個 Libary\Db
        echo  __NAMESPACE__ . ' static function test ';
    }
    public static function getInstance(){
        echo __NAMESPACE__ . ":getInstance";
    }
}

App/Android/Name.php
namespace App\Android;
class Name
{
    public function __construct()
    {
        echo __NAMESPACE__ . "";
    }
    public static function test()
    {
        echo  __NAMESPACE__ . ' static function test ';
    }
}

輸出:
Libary\Db
Libary:getInstance
Libary\Db
Libary static function test
App\Android static function test

s


...using __autoload() is discouraged and may be deprecated or removed in the future.
使用 __autoload() 是將會被棄用的(PHP 7.2.0),因為只能使用一次。應使用spl_autoload_register() 

use Libary\Db\MySQL;  // use 使用絕對路徑,放在class外面,namespace後面

參考資料:
https://www.zybuluo.com/phper/note/66447  PHP中的自动加载
https://stackoverflow.com/questions/12702527/how-to-call-autoload-in-php-code  How to call __autoload() in php code
https://stackoverflow.com/questions/33342994/unexpected-use-t-use-when-trying-to-use-composer  unexpected 'use' (T_USE) 
https://www.php-fig.org/psr/psr-0/  PSR-0: Autoloading Standard