[modern web 2016] 讓你的 php 開發流程再次潮起來
TRANSCRIPT
{ "name": "shengyou/self-‐introduction", "description": "個⼈人簡介", "authors": [ { "name": "范聖佑 (Shengyou Fan)", "email": "[email protected]", "homepage": "http://www.shengyoufan.com", "company": "得寬科技 (The Qwan)", "role": ["研究員", "Laravel 傳教⼠士"] } ], "support": { "facebook": "http://fb.me/shengyoufan", "twitter": "@shengyou", } }
Laravel 台灣https://www.facebook.com/groups/laravel.tw
成⽴立於 2013 現有 4000 位成員加⼊入
2016.03正修科⼤大資⼯工系
2016.01中華電信學院
2015.07新北市樹林國⼩小
2014.12彰師⼤大資⼯工系
2015.05臺中科⼤大資⼯工系
2015.12⻁虎尾科⼤大電算中⼼心
2016.02交通⼤大學資服中⼼心
VagrantfileVagrant.configure(2) do |config| config.vm.box = "ubuntu/trusty64"
config.vm.provider "virtualbox" do |vb| vb.customize ["modifyvm", :id, "-‐-‐memory", "512"] vb.customize ["modifyvm", :id, "-‐-‐cpus", "1"] vb.customize ["modifyvm", :id, "-‐-‐natdnsproxy1", "on"] vb.customize ["modifyvm", :id, "-‐-‐natdnshostresolver1", "on"] end
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "private_network", ip: "192.168.10.10" end
模擬多主機環境Vagrant.configure(2) do |config| config.vm.define :app do |app| app.vm.box = "ubuntu/trusty64" # ... app.vm.network "private_network", ip: "192.168.10.10" end
config.vm.define :db do |db| db.vm.box = "ubuntu/trusty64" # ... db.vm.network "private_network", ip: "192.168.10.11" end end
客製化需求
# VM 外 $ vagrant ssh
# VM 裡 $ apt-‐get install ... $ exit
# VM 外 $ vagrant package $ vagrant box add {box name} package.box
# 在專案資料夾內 $ vagrant init {box name} $ vagrant up
1. 先客製化 Box 2. ⽤用客製化 Box 開機器
Homestead• Laravel 官⽅方使⽤用 VitrualBox 及 Vagrant 技術製作出來的
VM Box• 可以迅速在本機上建置開發 Laravel 所需的多合⼀一環境• 不僅可以拿來開發 Laravel,拿來開發其他 PHP 專案也是可以的!
• 套件新、更新勤、抽換快!
Homestead 操作流程# 下載 Homestead $ git clone ...homestead.git Homestead $ cd Homestead $ bash init.sh
# 設定 Homestead $ vim ~/.homestead/Homestead.yaml folders: -‐ map: {本機資料夾} to: {VM 內資料夾} sites: -‐ map: {開發⽤用網址} to: {VM 內網站根⺫⽬目錄}
# 設定 Host $ vim /path/to/hosts
# 啟動 Homestead $ vagrant up
客製化 Homestead
#!/bin/sh if [ ! -‐f /usr/local/extra_homestead_software_installed ]; then echo "installing some extra software"
sudo -‐s apt-‐get install -‐y ...
touch /usr/local/extra_homestead_software_installed else echo "extra software already installed... moving on..." fi
$ vim ~/.homestead/after.sh
不能⽤用 VM 的特殊狀況…• Laravel 道場 拿來做教學的訓練機• 整合 Cmder、UwAmp、git、Composer 等多項開放源始碼⼯工具於⼀一體
• 獨⽴立的環境變數、port 設定• 免安裝、免設定、解壓縮即可使⽤用!• 在會重開機⾃自動還原的環境下特別好⽤用!
⺫⽬目前正式發佈 v1.3.0 穩定版http://www.laravel-dojo.com/opensource/wagon
wagon 懶⼈人包
git 也是有 GUI 的
https://www.gitkraken.com/https://www.sourcetreeapp.com
git 也是可以接 svn 的# 把 svn 裡的檔案取出來 $ git svn clone -‐r HEAD {SVNSERVER} {folder}
# git ⽇日常指令 $ cd {folder} $ vim {file} $ git add {file} $ git commit -‐a -‐m '{message}'
# 同步到 svn 上 $ git svn rebase $ git svn dcommit
git 也是可以⽤用 FTP 部署的
https://ftploy.com/https://git-‐ftp.github.io/
常⾒見錯誤幼幼班 初級班 進階班
• 沒存檔• 漏分號 ;• 漏其符號 , ' " • 括號 { [ ( 沒成對或範圍錯誤
• 基本 PHP 語法錯誤
• 物件沒實體化就使⽤用
• 沒有⽤用 use (漏namespace)
• 重覆的 function 名稱
• undefine function
• 沒有 return 或return 的型別不對
• 重複的邏輯• Interface 定義了但忘了實作
除錯⼯工具• ⼟土炮式掃雷
- echo (!) → exit
- var_dump() → exit
- print_r() → exit
• Laravel 式除錯 - dd() (底層為 Symfony VarDumper)
永遠扯不清的 include 地獄// 需要⽤用到 lib1 裡的 Class include __DIR__ . "/libs/lib1/Class.php";
// lib1 相依於 lib2 及 lib3 include __DIR__ . "/libs/lib2/Class.php"; include __DIR__ . "/libs/lib3/Class.php";
// lib2 相依於 lib4 include __DIR__ . "/libs/lib4/Class.php";
// lib3 相依於 lib999 include __DIR__ . "/libs/lib999/Class.php";
寫 new 寫得提⼼心吊膽// 實體化 Class $class = new MyClass();
// 載⼊入 Class 失敗 PHP Fatal error: Class 'MyClass' not found in scripts.php on line x
優化⽅方向• 不再落⼊入 include / require 地獄
- 解決裝 A 掉 B 少 C 的困境• ⾃自動載⼊入專案內的 類別 / 函式
- 使⽤用類別時不⽤用⼿手動 include- 使⽤用⾃自定函式不⽤用⼿手動 include
優化⽅方向• 以專案為基礎的套件管理機制
- 各專案內的套件獨⽴立管理,不互相影響• ⾃自動化下載、更新套件
- 統⼀一的套件版本管理機制- 流程由⾃自動化⼯工具輔助
• 加⼊入 PHP 的套件⽣生態系 - 不再重新發明輪⼦子
安裝 Composer
# 下載安裝指令 (請使⽤用官網完整指令) $ php -‐r "copy('https://getcomposer.org/installer', ...);" $ php -‐r "..." $ php composer-‐setup.php $ php -‐r "unlink('composer-‐setup.php');"
# 將 Composer 變成系統全域指令 $ mv composer.phar /usr/local/bin/composer
# 使⽤用 Composer $ [php] composer[.phar] {command}
https://getcomposer.org/download/
增加 composer.json
{ "name": "{vendor}/{project}", "type": "project", "license": "proprietary", "authors": [ { "name": "{author name}", "email": "{author email}" } ], "minimum-‐stability": "stable", "require": {} }
$ composer init
nesbot/carbon<?php
require __DIR__ . '/vendor/autoload.php';
// 像 Facebook ⼀一樣顯⽰示時間間距 use Carbon\Carbon;
$past = Carbon::yesterday() -‐>addHours(4) -‐>minute(30);
Carbon::setLocale('zh-‐TW');
echo $past-‐>diffForHumans(Carbon::now()); // ⼀一天前
intervention/image<?php
require __DIR__ . '/vendor/autoload.php';
// 處理圖⽚片縮⼩小轉存 use Intervention\Image\ImageManagerStatic as Image;
Image::configure(array('driver' => 'imagick'));
$image = Image::make('public/foo.jpg') -‐>resize(300, 200) -‐>save('public/bar.png', 60);
recca0120/laravel-‐tracy
<?php
require __DIR__ . '/vendor/autoload.php';
use Recca0120\LaravelTracy\Tracy;
Tracy::instance();
// tracy.php <?php
$config = [ 'enabled' => true, 'showBar' => true, 'accepts' => [ 'text/html', ], 'editor' => 'vscode://open?url=file://%file&line=%line', 'panels' => [ '{name}' => {boolean}, ], ];
VS Code URL Handlerhttps://github.com/shengyou/vscode-‐handler
三⼤大⾃自動載⼊入⽅方式{ "autoload": { "psr-‐4": {...}, "classmap": [...], "files": [...] }, "autoload-‐dev": { "psr-‐4": {...}, "classmap": [...], "files": [...] }, }
$ vim composer.json
// app/MyAwesomeClass.php namespace App;
class MyAwesomeClass extends SuperPower { // ... }
MyAwesomeClass.php
app
composer.json
// index.php <?php
require __DIR__ . '/vendor/autoload.php';
use App\MyAwesomeClass;
$awesomeness = new MyAwesomeClass();
?>
autoload.php
vendor
index.php
composer.json
autoload.php
vendor
index.php
libs
my-‐old-‐school-‐class.php
// classes/my-‐old-‐school-‐class.php class my_old_school_class { // ... }
// index.php <?php
require __DIR__ . '/vendor/autoload.php';
$oldschool = new my_old_school_class();
?>
以 files 載⼊入$ composer dump-‐autoload
{ "autoload": { "files": { "helpers/my-‐functions.php" }, } }
// helpers/my_functions.php <?php
if (! function_exists('super_power')) { function super_power() { //... } }
// index.php <?php
require __DIR__ . '/vendor/autoload.php';
?> <!-‐-‐ ... -‐-‐> <h1><?php super_power() ?></h1> <!-‐-‐ ... -‐-‐>
my_functions.php
helpers
composer.json
autoload.php
vendor
index.php
建⽴立私有套件# 建⽴立專案⺫⽬目錄 $ mkdir secret-‐ingredient $ cd secret-‐ingredient $ git init $ composer init $ composer install
# 撰寫套件程式碼 $ vim ... $ git commit ...
# 設定套件版本並發佈 $ git tag -‐a 1.0 $ git push ...
設定 Repositories{ "require": { "{vendor}/{project}": "{version}" }, "repositories": [ { "type": "vcs", "url": "{url}" } ], }
$ composer update
套件資訊庫 / Proxy
https://toranproxy.com/https://github.com/composer/satis
優化⽅方向• 導⼊入 Migration ⼯工具
- 透過撰寫程式碼的⽅方式紀錄資料庫異動- 部份⼯工具也有 Seeding 的設計,輕鬆產⽣生測試資料- 部份⼯工具也有對應的 ORM,可⼀一併處理 Model
Laravel 的 Migration$ artisan make:migration {name}
// database/migrations/{migration}.php public function up() { Schema::{create|table}('{table}', function (Blueprint $table) { $table-‐>increments('id'); $table-‐>{column type}('{column name}'); $table-‐>timestamps(); }); }
資料庫變更回溯
// database/migrations/{migration}.php public function down() { // 寫跟 up() 反向的動作 Schema::drop('{table}');
Schema::table('{table}', function (Blueprint $table) { $table-‐>dropColumn('{column}'); }); }
$ artisan migrate $ artisan migrate:rollback
填充測試資料$ artisan make:seeder {name} $ artisan db:seed// database/seeds/{seeder}.php public function run() { {Model}::truncate();
foreach(range(1, 10) as $number) { {Model}::create([ '{column}' => '{value}', ]); } }
Phinx$ composer require robmorgan/phinx $ vendor/bin/phinx init
/// phinx.yml paths: migrations: %%PHINX_CONFIG_DIR%%/db/migrations seeds: %%PHINX_CONFIG_DIR%%/db/seeds
environments: default_migration_table: phinxlog default_database: development production: # ... development: # ... testing: # ...
Phinx Migration$ vendor/bin/phinx create {name} $ vendor/bin/phinx migrate $ vendor/bin/phinx rollback
// db/migrations/{migration}.php public function change() { $table = $this-‐>table('{name}'); $table-‐>addColumn('{column}', '{data type}') -‐>create(); }
Phinx Seeding$ vendor/bin/phinx seed:create {name} $ vendor/bin/phinx seed:run
/// db/seeds/{seeder}.php public function run() { $data = []; foreach(range(1, 10) as $number) { $data[] = [ // ... ]; } $this-‐>insert('{table}', $data); }
先從資料庫開始
只要 PDO ⽀支援的都可以!
// config/database.php return [
'default' => '{connection}',
'connections' => [ 'sqlite' => [/* ... */], 'mysql' => [/* ... */], 'pgsql' => [/* ... */], 'sqlsrv' => [/* ... */], ],
];
原⽣生不⽀支援的,也有 Package
https://github.com/yajra/laravel-‐oci8http://php.net/oci8
最新流⾏行的,也有 Package
https://github.com/jenssegers/laravel-‐mongodb
時下流⾏行的也有 Package 可⽤用
反轉 DB 結構成 Migration/Seed• 將現有 DB 結構反轉成 Migration
- https://github.com/Xethron/migrations-generator - https://github.com/nWidart/DbExporter - https://gist.github.com/bruceoutdoors/9166186
• 將現有 DB 資料反轉成 Seed - https://github.com/orangehill/iseed
P.S 無法完美轉換,尤其是 Relation 的部份,建議轉出來後⼿手動調整
讓 Eloquent 相容現有 DB
class ExisteingDbModel extends Model { // 指定使⽤用的 DB (依照 config/database.php 的設定 protected $connection = '{connection}';
// 指定 Model 對應的 table protected $table = '{table}';
// 指定 table 內的主鍵 protected $primaryKey = '{column}'; }
設定 column 預設值及型別class ExisteingDbModel extends Model { protected $attributes = [ '{column}' => {value}, ];
protected $cast = [ '{column}' => '{data type}', ]; }
讓 Eloquent 轉換 column 名稱class ExisteingDbModel extends Model { // Accessors public function get{Column}Attribute() { return $this-‐>attributes['{column}']; }
// Mutators public function set{Column}Attribute($value) { $this-‐>attributes['{column}'] = $value; } }
相容⽇日期/時間的 columnclass ExisteingDbModel extends Model { // 設定需要轉換為 date/time 的 column protected $dates = ['{column}'];
// 設定 DB 內⽇日期時間格式 protected $dateFormat = '{format}';
// 設定 created_at 及 updated_at 對應到的 column const CREATED_AT = 'created_at'; const UPDATED_AT = 'updated_at';
// 設定 Eloquent 是否啟動 created_at 及 updated_at public $timestamps = {boolean}; }
新/舊版網站共存• 先在新版 (Laravel 版) 實作⾸首⾴頁,取代 index.php
- 更換 HTTP 伺服器的 Document Root• 把舊版 (Page Script 版) 的前台網站直接放到 Laravel 專案的 public 資料夾裡 - HTTP 伺服器優先使⽤用已經存在實體檔案的 Page Script
P.S 也可以視情況使⽤用 symbolic link 讓新/舊版共存$ ln -‐s path/to/page-‐script-‐ver path/to/laravel/public
先重做後台// 設定 app/Http/routes.php // 把後台放到前置詞底下 Route::group(['prefix' => 'admin'], function() { // 實際上的 URL 會是 "/admin/{uri}" Route::get('{uri}', [/* ... */]); });
// 把後台放到 sub-‐domain 去 Route::group(['domain' => '{domain name}'], function() { // 在 {domain name} 底下才有辦法看到此 Route Route::get('{uri}', [/* ... */]); });
抽換功能改⽤用套件⽣生態系• Laravel 已經有做的功能就不要⾃自⼰己再重做⼀一次
- Route、MVC、Mail、Template Engine、Cron• Laravel 沒有的功能也先找有沒有套件已經做過
- https://github.com/chiraggude/awesome-laravel - https://github.com/summerblue/laravel-package-top-100
使⽤用 Route 處理舊版網址// app/Http/routes.php Route::get('{all}', [/* ... */]) -‐>where('all', '.*');
// app/Http/Controllers/SeoController.php class SeoController extends Controller { public function reroute($uri) { // ... return redirect('{uri}', 301); } }
置⼊入性 Programming只要能⽤用 Composer 裝得都好說!
{ "require": { "php": ">=5.5.9", "illuminate/view": "^5.2", "illuminate/database": "^5.2", "illuminate/events": "^5.2", "illuminate/pagination": "^5.2", "illuminate/http": "^5.2", "recca0120/laravel-‐tracy": "^1.5" } }
載⼊入 Laravel 元件// bootstrap.php use Illuminate\Database\Capsule\Manager as Capsule;
$capsule = new Capsule; $capsule-‐>addConnection([ 'driver' => 'mysql', 'host' => 'host', 'database' => 'database', // ... ]); $capsule-‐>setAsGlobal(); $capsule-‐>bootEloquent();
$ composer require illuminate/database
嫌太⿇麻煩?
{ "require": { "recca0120/laravel-‐bridge": "^1.0.0" } }
寫好 Pacakge 等你裝!
$ composer require recca0120/laravel-‐bridge
// bootstrap.php <?php
use Recca0120\LaravelBridge\Laravel;
require __DIR__.'/vendor/autoload.php';
$connections = [ // ... ];
Laravel::instance() -‐>setupView(..., ...) -‐>setupDatabase($connections) -‐>setupPagination() -‐>setupTracy([ 'showBar' => true ]); ★ 跟 Codeigniter 整合範例:
https://github.com/recca0120/laraigniter
優化時機• 建⽴立環境時
- ⽤用 Bash、⽤用 Automation Tool 來建⽴立環境• 建⽴立專案時
- 使⽤用 composer create-‐project 簡化專案啟動步驟• 部署專案時
- 使⽤用 task runner 協助部署動作
⾃自動化安裝環境⾄至少可以先從 Bash 開始!
https://goo.gl/Ntf9lrhttps://github.com/laravel/settler
Composer Script Events"scripts": { "post-‐root-‐package-‐install": [ "php -‐r \"copy('.env.example', '.env');\"" ], "post-‐create-‐project-‐cmd": [ "php artisan key:generate" ], "post-‐install-‐cmd": [ "Illuminate\\Foundation\\ComposerScripts::postInstall", "php artisan optimize" ] },
$ composer run-‐script {event}
⾃自動化部署
Local
$ envoy run deploy
Server 3Server 1 Server 2
Cloud Servers // Envoy.blade.php @servers(['web' => '192.168.1.1'])
@task('deploy', ['on' => 'web']) cd site git pull origin {{ $branch }} php artisan migrate @endtask
讓你的 PHP 專案潮起來!
優化環境建置流程
升級開發⼯工具
導⼊入Composer
導⼊入Migration
導⼊入適⽤用框架
導⼊入⾃自動化
透過可獨⽴立各別導⼊入的施⼒力點,⼀一步⼀一步現代化你的專案!