胖胖 Model 的減重方法:Form

Web application常常需要對使用者輸入的參數做驗證。

這些驗證(validation)logic該放哪裡呢?

現代框架通常會提供工具協助這件事,

以Laravel來說,使用內建的Validator可以在controller內這麼做:

$validation = Validator::make(
    array(
        'name' => Input::get( 'name' ),
        'email' => Input::get( 'email' ),
    ),
    array(
        'name' => array( 'required', 'alpha_dash' ),
        'email' => array( 'required', 'email' ),
    )
);
 
if ( $validation->fails() ) {
    $errors = $validation->messages();
}

同樣的validation logic只出現一次還好,出現在不同controller就會導致這段code duplicate。

該怎麼辦呢?

試試看放進model:

class Ball extends Eloquent
{
    private $rules = array(
        'color' => 'required|alpha|min:3',
        'size'  => 'required',
        // .. more rules here ..
    );

    public function validate($data)
    {
        // make a new validator object
        $v = Validator::make($data, $this->rules);
        // return the result
        return $v->passes();
    }
}

controller內的code就會簡化成這樣:

$b = new Ball();

if ($b->validate(Input::all())){
    // success code
}else{
    // failure code
}

還算OK的作法…如果你能接受將validation logic視為business logic的話。

這類的code有比controller和model更適合的地方。寫一個form class即可:

class ArticleForm
{
    protected $validationRules = [
        'title' => 'required',
        'content' => 'required',
    ];
    protected $inputData;
    protected $validator;

    public function __construct($input)
    {
        $this->inputData = $input;
    }

    public function isValid()
    {
        $this->validator = Validator::make($this->input, $this->validationRules);
        return $this->validator->passes();
    }
    
    public function getErrors()
    {
        return $this->validator->errors();
    }

}

controller內直接呼叫即可,看起來readable許多:

$form = new ArticleForm( Input::all() );
 
if ( ! $form->isValid() ){
    return Redirect::back()->with( [ 'errors' => $form->getErrors() ] );    
}
 
$article = new Article( Input::only('title', 'content', 'status') );
 
$article->save();

如果多個model都有form classs的話,可以將共用的code抽出來成為抽象類別:

abstract class FormModel
{
    protected $validationRules;
    protected $inputData;
    protected $validator;

    public function __construct($input)
    {
        $this->inputData = $input;
    }

    public function isValid()
    {
        $this->validator = Validator::make($this->input, $this->validationRules);
        return $this->validator->passes();
    }
    
    public function getErrors()
    {
        return $this->validator->errors();
    }

}

這樣原本的form class可以簡化到只剩下validation rules:

class ArticleForm extends FormModel
{
    protected $validationRules = [
        'title' => 'required',
        'content' => 'required',
    ];
}

甚至能寫多種validation rules給不同controller:

   protected $happyArticleValidationRules;
    protected $angryArticleValidationRules;
    protected $funnyArticleValidationRules;

    public function isValidHappy()
    {
        $this->validator = Validator::make($this->input, $this->happyArticleValidationRules);
        return $this->validator->passes();
    }
    
    public function isValidAngry()
    {
        $this->validator = Validator::make($this->input, $this->angryArticleValidationRules);
        return $this->validator->passes();
    } 
   
    public function isValidFunny()
    {
        $this->validator = Validator::make($this->input, $this->funnyArticleValidationRules);
        return $this->validator->passes();
    }

實作上可以參考Laravel官方論壇原始碼

(注意:若您是用Laravel 5,Request的封裝可以取代這件事。)

下次你的application包含大量validation logic時,不妨試試看這個作法。

(Photo via Susanne Nilsson, CC licensed)

胖胖 Model 的減重方法:Repository

開發application常常要對資料庫做各種不同條件的查詢。

這類的查詢code很容易到處重複。

以 Laravel Eloquent來說,controller很容易出現這樣的code:

$users = User::where('votes', '>', 100)
                        ->whereGender('W')
                        ->orderBy('created_at')
                        ->get();

這段「取出受歡迎的女性使用者」的code,在中小型application還可以接受,

一旦規模稍大就會在不同controller到處重複(duplicate)。

利用內建的query scope功能,雖然能夠稍微改善,但還是不夠:

$users = User::popular()->women()->orderBy('created_at')->get();

不只如此,還會讓model裡面多出定義query scope的程式碼,導致model變胖:

class User extends Eloquent {

    public function scopePopular($query)
    {
        return $query->where('votes', '>', 100);
    }

    public function scopeWomen($query)
    {
        return $query->whereGender('W');
    }

}

這種時候,適合寫一個Repository進行封裝:

class UserRepository
{
    public function getPopularWomen()
    {
        return User::where('votes', '>', 100)->whereGender('W')->orderBy('created_at')->get();
    }
}

它會讓controller內的code簡潔、可讀許多:

$repository = new UserRepository();

$users = $repository->getPopularWomen();

如果您是 Laravel 使用者,這種搭配Automatic Resolution的寫法一定經常看到:

class UserController extends BaseController
{
    protected $users;
    
    public function __construct(UserRepository $repository)
    {
        parent::__construct();
        
        $this->users = $repository;
    }
    
    public function getIndex()
    {
        $women = $this->users->getPopularWomen();
    }
}

甚至可以寫一個共用的abstract class減少duplicate code:

model = $model;
    }
    
    public function getById($id)
    {
        return $this->model->find($id);
    }
    
    public function getAll()
    {
        return $this->model->all();
    }

    public function save($data)
    {
        if ($data instanceOf Model) {
            return $this->storeEloquentModel($data);
        }
    }
    
    public function saveMany($collection)
    {
        foreach($collection as $model)
        {
            $this->storeEloquentModel($model);
        }
    }

    public function delete($model)
    {
        return $model->delete();
    }

    protected function storeEloquentModel($model)
    {        
        if ($model->getDirty()) {
            return $model->save();
        } else {
            return $model->touch();
        }
    }

}

透過Repository封裝還有一個好處:

測試controller或是service的時候可以抽換掉Repository,讓測試時不用碰到資料庫!

下次你的application包含大量query logic時,不妨試試看這個作法。

(Photo via Susanne Nilsson, CC licensed)

胖胖 Model 的減重方法:Presenter

開發application時,常常會有大量的呈現(presentation)邏輯。

像是日期格式、金額格式(是否穿插逗號)、名稱(是否需要大寫)等等。

把這些logic寫在model的話,很容易就會導致model過胖。

舉例來說,如果文章model在不同地方需要呈現日期、日期時間、西方格式、台灣格式,

model就會長這樣(以Laravel Eloquent舉例):

class Article extends Eloquent
{
    public function getDate(){/*...*/}

    // 呈現給台灣地區的時間格式
    public function getTaiwaneseDateTime(){/*...*/}

    // 呈現給歐美地區的時間格式
    public function getWesternDateTime(){/*...*/}

    public function getTaiwaneseDate(){/*...*/}

    public function getWesternDate(){/*...*/}
}

要將這類presentation logic抽出來,許多框架採用了「helper function」的作法,

也就是定義好幾個functions用來處理呈現邏輯。

除了helpers的作法之外,還有一個非常簡單有效的方法。

也就是使用Decorator pattern來封裝出presenter,像這樣:

class ArticlePresenter
{
    protected $article;

    public function __construct(Article $article)
    {
        $this->article = $article;
    }

    // 呈現給台灣地區的時間格式
    public function getTaiwaneseDateTime(){
        return date('Y-m-d', $this->article->created_at);
    }

    // 呈現給歐美地區的時間格式
    public function getWesternDateTime(){/*...*/}

    public function getTaiwaneseDate(){/*...*/}

    public function getWesternDate(){/*...*/}
}

概念上只是把原本的model從constructor傳進去,原本的function搬過去,

把$this改寫成$this->article即可。

然而,這樣會導致view裡面需要初始化物件:

@foreach($articles as $article)
    
    發文日期:{{ $presenter->getTaiwaneseDate() }}
@endforeach

在view裡面做這種事,怎麼看都是很醜的。

多寫一個function即可改善:

class Article extends Eloquent
{
    public function present()
    {
        return new ArticlePresenter($this);
    }
}

view內會變成這樣:

@foreach($articles as $article)
    發文日期:{{ $article->present()->getTaiwaneseDate() }}
@endforeach

新的問題來了:多次呼叫present會浪費記憶體。

可以這樣改善:

class Article extends Eloquent
{
	protected $presenterInstance;

	public function present()
	{
		if ( ! $this->presenterInstance)
		{
			$this->presenterInstance = new ArticlePresenter($this);
		}

		return $this->presenterInstance;
	}

}

多個model都使用presenter的話,這段code會重複。善用PHP trait可以解決這個問題:

trait PresentableTrait {

	protected $presenterInstance;

	public function present()
	{
                // ...

		if ( ! $this->presenterInstance)
		{
			$this->presenterInstance = new $this->presenter($this);
		}

		return $this->presenterInstance;
	}

} 

Laravel社群至少有3套成熟的presenter實作:

https://github.com/laracasts/Presenter

https://github.com/robclancy/presenter

https://github.com/laravel-auto-presenter/laravel-auto-presenter

有的功能豐富、有的比較陽春,但是概念上一樣,都是decorator pattern的應用。

下次你的application包含大量presentation logic時,不妨試試看這個作法。

(Photo via Susanne Nilsson, CC licensed)

胖胖 Model 減重的五個方法

隨著軟體專案不斷增加新功能,你是否隱約感到不安?覺得Model越來越胖、開發速度越來越慢?

一元翻譯用Laravel 4開發半年多,我也遇到同樣問題。

上禮拜受PHP 也有 Day的邀請,

跟大家分享了幾個我在工作上找到的改善方法,分別是:

  • Presenter
  • Repository
  • Form
  • Service
  • Package

投影片在此

影片在此

投影片是以Laravel 4當範例,但是概念上通用於任何語言、框架。

歡迎在這邊留言和我討論,有問題想問也可以。謝謝各位。

ARCA架構:以 Active Record 為軸心的架構

越來越多的framework本身提供Active Record實作。

不管是Rails的Active Record,還是Laravel的Eloquent,

這些Active Record實作的功能越來越豐富而強大。

它讓開發速度更快了,但也因為太多功能塞在一起,容易寫出極度肥胖的萬能類別。

該怎麼使用這種萬能類別,又不至於讓它胖到難以維護呢?

我從工作中整理出了稱之為ARCA的架構。今天來跟各位分享。

註:本文程式碼多以Laravel框架舉例。但是相關概念通用在任何擁有Active Record的語言/框架。

簡介

ARCA全名「Active Record Centered Architecture」,

也就是「Active Record中心架構」,以Active Record為主軸的軟體架構。

ARCA試圖在享受Active Record帶來的開發速度的同時,拉出一定的架構。

這套架構適用於中型以上(連續開發3個月以上)專案,不適用於小型專案(像是餐廳官網)。

ARCA架構

ARCA架構由幾個元素組成,檔案結構看起來如下(以MVC舉例):

一個元素會是一個資料夾,或是一個檔案(類別)。

/View資料夾
/Controller資料夾
/Model資料夾
…./Entity Model資料夾
……../Entity類別
……../Repository類別
……../Presenter類別
……../Form類別
…./Service資料夾
……../Service類別
…./Operation Model資料夾

Entity Model資料夾

擺放與一種entity相關概念的程式碼。

將一種entity相關的程式碼放在同一個資料夾。

Entity類別

entity是公司在乎的商業領域(domain)中,代表現實世界的一種事物。

常見的entity包含訂單、商品、顧客、禮券、文章。

通常會代表現實生活中的一種事物、需要具備ID。

在ARCA中負責直接繼承Active Record的類別。

這是ARCA架構的重點所在。遵照少數的類別命名、資料表命名慣例(convention),

直接賦予這些entity類別多種能力:

  • 物件property存進資料庫的相關操作(CRUD)
  • entity間的relationship
  • 對資料庫取出的值做適當轉換

而開發者所要做的,只是寫好類別定義與幾個函式而已。

class User extends Eloquent {

    public function phone()
    {
        return $this->hasOne('Phone');
    }

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

}

也正是Active Record的多功能,讓entity的建立如此迅速。

公司domain所需的商業行為,與entity相關的動作都是寫在此處。

class User extends Eloquent {
    // ...
    public function addComment()
    {
        // ...
    }
}

Repository類別

封裝query logic,提供取得entity(一或多個)的不同方法。

不論是Rails的Active Record還是Laravel的Eloquent,都有提供查詢(query)的功能。

舉例來說,想要拿出新舊排序過的精選文章,可以這樣寫:

Article::where('featured', true)
            ->where('status', '=', Article::STATUS_PUBLISHED)
            ->orderBy('published_at', 'desc')
            ->get();

但是這幾行程式碼很可能常常用到,會到處重複。

這時就可以用repository封裝這段code:


class ArticleRepository
{
    public function getFeaturedArticles($count = 3)
    {
        return Article::where('featured', true)
                           ->where('status', '=', Article::STATUS_PUBLISHED)
                           ->orderBy('published_at', 'desc')
                           ->take($count)
                           ->get();
    }
}

以這段封裝來講,不但讓controller內的code可讀性提昇,同時還提供取出不同數量的參數功能。

// in controller
$repository = new ArticleRepository();

$articles = $repository->getFeaturedArticles(10);

ARCA內的repository都依賴Active Record提供的query實作,

所以建立一個abstract class再讓所有repository都繼承它是一個好主意。

參考這份EloquentRepositoryArticleRepository實作。

Presenter類別

封裝與business logic無關,只與呈現(presentation)相關的logic。

entity相關的名稱、金額、日期,常常在不同地方有不同呈現格式。

將這些寫成entity的function可不是好主意。

這類presentation logic可以獨立成presenter類別。

(或稱View Objects…。注意:軟體名詞在不同社群的用法極度混亂。)

概念上只是將entity塞進presenter,也就是decorator pattern

實作上可以依不同語言、框架的特色發揮創意。

像是這套善用了PHP特性,讓presentation 相關的code明確分離。

Presenter長這樣:

class UserPresenter extends Presenter {
    public function fullName()
    {
        return $this->first . ' ' . $this->last;
    }
}

接著可以從entity直接使用presenter實體,呼叫presentation logic。

$user->present()->fullName

Form類別

處理使用者從HTTP表單填入的資料,封裝驗證(validation) logic。

web application少不了大量的運用html表單與使用者互動。

針對user input的驗證 logic(例如字串長度、日期、金額大小)可以抽出來成為Form類別。

(在Laravel 5稱為Form Request,在Rails社群稱為Form object

原理上來說,就是將表單傳來的參數傳給Form類別,在裡面驗證過後才決定是否執行下一步驟。

舉例來說,這件事若在controller內進行,看起來就像:

$form = new ArticleForm( Input::all() );

if ( ! $form->isValid() ){
    return Redirect::back()->with( [ 'errors' => $form->getErrors() ] );    
}

$article = new Article( Input::only('title', 'content', 'status') );

$article->save();

而Form類別的實作則依照語言/框架的不同各自發揮。

舉例:Rails可用VirtusLaravel可應用Validator

Service資料夾

擺放跨domain概念的程式碼。

多個service類別會放在此資料夾。

Service類別

概念上不屬於任何entity的business logic可以獨立成service。

通常是施加在多個entity上(來自不同domain)的複雜行為。

舉例來說,計算訂單的折扣與售價,通常會涉及訂單與禮券,並且運算邏輯較為複雜:

class PriceService
{
    public function calculate(Order $order, Coupon $coupon)
    {
        // ...
    }
}

使用時,將entity傳進去即可:

// in somewhere, maybe a controller

$order = /* get the order */;

$coupon = /* get the coupon */;

$service = new PriceService();

$service->calculate($order, $coupon);

Operation Model資料夾

圍繞某種行為概念的程式碼可以整理起來、放在一起。

當service開始依賴其他service時,代表這項行為開始變得複雜、開始造成理解的困難。

試著看清是哪個概念(這很不容易)、以它命名(也不容易),建立出Operation model。

舉例來說,電子商務公司提供報價會同時涉及「禮券折扣」、「出貨日期」、「總金額」的計算。

也就是QuotationService(報價)會用到DiscountService(計算折扣金額),

DueDateService(計算出貨日期), PriceService(計算總金額)。

這時可以將它們從Service資料夾移出、建立一個Quotation資料夾、再全部丟進去。

丟進去之後可以進行適當的refactoring,增加這幾個類別間的cohesion,降低之後理解的困難。

也可以進一步使用Facade Pattern封裝這些類別,建立一個統一對外的接口,

降低其他地方的程式碼與這個Operation Model的coupling。

以上面報價的例子來說:

class QuotationManager // 我決定將它直接視為某種管理員,不再視為單純的service
{
    
    protected $due;
    
    protected $price;

    protected $discount;
    
    public function __construct(DueDateService $due, PriceService $price, DiscountService $discount)
    {
        $this->due = $due;
        
        $this->price = $price;
        
        $this->discount = $discount;
    }
    
    public function calculate(Order $order)
    {
        // 可能依序執行某些business logic,也可能只是直接呼叫底下的service
        /*
        $this->due->calculate($order);
        
        $this->price->calculate($order);
        */
    }
    
    public function discount(Order $order, Coupon $coupon)
    {
        // 可能依序執行某些business logic,也可能只是直接呼叫底下的service
        // $this->discount->calculate($order, $coupon);
    }
   
}


結語

討論架構時,通常只討論概念,不將檔案結構列入討論。

我認為那樣的討論讓很多人有看沒有懂,所以將檔案結構一併納入ARCA說明了。

ARCA不是一種理論或是無敵的架構,只是一種用來搭配Active Record,

將常用元素(或說是Design Pattern)組合起來的一種方式而已。

可以將ARCA架構視為一種適合新創公司快速開發的、通用的中型基本架構。

隨著情況不同,做出調整、增減元素(例如好用的Factory類別)即可。

有任何疑問,或是有推薦的好用Design Pattern,覺得適合加進ARCA嗎?

歡迎在下方留言,或是在 Twitter @howtomakeaturn 和我討論。


Q&A

Q1: Service類別的例子怪怪的…明明可以寫進其中一種entity啊!像是這樣就可寫進訂單entity:

class Order extends Eloquent
{
    public function calculatePrice(Coupon $coupon)
    {
        // ...
    }
}

何時放在entity,何時獨立成service呢?

如你所說,一項行為究竟是否足夠「複雜」到需要獨立出來,的確屬於開發者的主觀判斷。

但是認定所有行為都不「複雜」的話,entity很容易變得極度肥胖喔。

Q2: 我覺得用Active Record會讓測試很難寫,有解法嗎?

這確實是Active Record的一大缺點:在測試時很難不去碰到database。

使用快速建立測資的套件,似乎是唯一能勉強接受的方法。

Ruby有factory_girl,PHP有Factory Muffin

參考看看吧。

(Photo via SuperCar-RoadTrip.fr, CC licensed)

關於Toptal的一點後續

我在去年8月加入了Toptal

之後在批踢踢收到幾位網友來信,好奇我在Toptal的後續發展。

我加入之後,並沒有在Toptal上面接案(我跑去一間startup上班了),

所以沒有實際的Toptal工作經驗。

但還是有一點心得跟各位分享。

Toptal上面case多嗎?

上面軟體專案的case挺多的,所以不用擔心加入之後「根本沒案子」。

容易接到case嗎?

我曾經花一個星期在上面找case,總共丟了4個case。

全都沒接到,連進入interview都沒有。

3個case被其他工程師拿走。

1個case得到recruiter答覆「I’m sorry, the client is considering only native English speakers.」

除此之外,從去年9月到現在,半年,我的profile都是保持開啟的,但是沒有case主動找上我。

只能說在上面接案的工程師不少,不是那麼好接。

我的中文能力會是優勢嗎?

目前客戶以歐美地區為主。

打開Join as a Client頁面,你會發現亞洲地區的公司想上去發case都不行。

所以,中文應該對接案沒什麼幫助。

申請加入要花多少時間?

會花不少時間。最後的project interview我花了大約2週,每天6小時開發才做完。

請參考我之前的文章:

TOPTAL申請經驗(上)

TOPTAL申請經驗(下)

加入要簽什麼樣的合約?

全部通過之後,會要求你用browser簽署一份電子合約。

其中關於隱私權(Toptal可以查看你在Toptal的Gmail信件內容)、商業機密的部份比較嚴格。

這有引起一些申請者的不滿,請參考:

My experience joining TopTal

合約上,我的時薪會是多少?

在申請的三次面試過程中會談到。

請參考官網FAQ:

How much does Toptal cost?

光看數字,比在台灣接案好很多。

以上,還有其他問題歡迎再來信詢問。

CodeIgniter不是一個現代框架

CodeIgniter在今年4月出了第三版。

我在Facebook、批踢踢至今沒看到有人撰文批評它,我覺得這樣不對。

要跟全世界的PHP工程師競爭,只學習CodeIgniter是不夠的。

CodeIgniter不是一個現代框架。必須有人指出這點。

CodeIgniter 3的釋出

這則新聞第一時間就在Reddit出現:

Codeigniter 3 is out

得分最高的評論如下:

是怎樣的被虐狂才會想在2015年用CodeIgniter來開發專案啊?沒有namespaces,沒有autoloading,沒有composer!我乾脆投河自盡好了。

這則評論看起來很偏激嗎?但它拿到最高評論可不只是因為搞笑。

CodeIgniter不是一個現代框架。它沒跟上PHP的幾個趨勢。

怎樣算是現代PHP框架?

現代框架沒有正式定義,但我認為至少要具備三點:

  • 符合PHP元件推薦規範(PHP Standard Recommendations)
  • 支援Composer套件管理
  • 提供成熟的測試環境

為什麼要有這三點才算是現代框架?

因為它們解決了PHP社群幾年來的三個大問題:

  • 一盤散沙的社群
  • 四散的套件
  • web需要軟體測試

一盤散沙的社群

不像Ruby社群,大家全力貢獻在Rails。

PHP社群四散各地、同一個功能在不同框架被重複開發、

框架間難以使用彼此的元件、不同社群難以使用其他社群開發的套件。

這樣的現象在近年終於獲得改善:各框架派出了幾位代表,大家協商出一套coding convention。

從此有了PHP框架互用性小組,制定了PHP元件推薦規範

以後若想要開發新框架,可以從現有的框架中拿幾個元件來組合著用。

PSR規範中對於namespace有清楚的規範,

而CodeIgniter 3中還是到處充滿CI_Controller、CI_Model這種過時的前綴字(prefix)寫法。

四散的套件

在開發套件的時候,PHP有元老級的PEAR、CodeIgniter的Sparks、

Laravel的Bundles、Zend Framework 2的Modules、CakePHP的Bakery。

這種現象是整個PHP社群的損失:PHP社群如此龐大,但是大家各搞各的,心力無法集中。

所幸,Composer的出現解決了這點。

現在大家都開發Composer套件了,整個PHP社群開始共享彼此的成果。

套件相依性的問題也在Composer一併解決,大幅加速了開發的速度與軟體的品質。

CodeIgniter 3還是沒直接支援Composer。

你還是要寫:

$this->load->library('xxx'); 

之類的囉唆東西。

web需要軟體測試

隨著web application越來越複雜、架構越來越龐大,

軟體測試開始成為web圈的討論重點。

軟體測試不但協助設計架構,確保軟體品質,還能協助重構。

web社群已經從「測試要寫很多很多」爭吵到「測試過多沒意義,足夠就好」。

DHH在2012年已經開始主張「測試覆蓋率不要追求100%」、

「code和測試的比例超過1比2就是出問題」

Testing like the TSA

他在2014年更進一步論述「測試先行已死」

TDD is dead. Long live testing.

PHP近年總算有了PHPUnit廣泛出現在各框架,大幅改善了測試環境。

而CodeIgniter呢?連像樣的基本測試都做不到。

結論

CodeIgniter的主要contributor,Phil Sturgeon,在2012年就曾經貼文闡述

「CodeIgniter不重寫就做不到的5件事」:

5 Things CodeIgniter Cannot Do (without a rewrite)

原文我就不翻譯了。他指出的五個缺失如下:

  • 自動載入
  • 命名空間
  • 資料庫結構抽象化
  • 單元測試
  • migration機制

綜合以上,我的結論就是:

要跟全世界的PHP工程師競爭,只學習CodeIgniter是不夠的。

CodeIgniter不是一個現代框架。


Q&A

Q1: 我耳聞Composer很久了,但到底怎麼入門呢?

COMPOSER設計原理與基本用法

COMPOSER進階原理:PHP命名空間與PSR-0


Q2: 我暫時不可能離開CodeIgniter,有什麼辦法嘗試軟體測試?

CI 2曾經有人把simpletest整合進去。不知道CI 3做不做得到就是了。

CodeIgniter with SimpleTest


Q3: CodeIgniter也許是有些小缺點,但真有那麼落後嗎?

我在2014年2月曾經寫信詢問Phil Sturgeon意見。他的答覆如下:

Hey Tony,

(上略)

我不會再理CodeIgniter了,它玩完了。試試看Laravel吧,然後看一下這些短片:

https://laracasts.com/

你會學到很多:)


Phil Sturgeon

原文

Hey Tony,

You definitely don’t want to rate yourself based on your own age and ability. I’m 25 and there are lot of people around who are younger and smarter, or older and smarter, and more successful. I rate myself based on my improvements over time, and as long as I’m always learning then I’m always getting better.

I’d ignore CodeIgniter completely, its done. Try getting into Laravel, and watch these screencasts:

https://laracasts.com/

You’ll learn a lot 🙂


Phil Sturgeon


社群看法

PHP台灣

by 阿川先生