본문 바로가기
프로그래밍/Web

Laravel 4 의 동적 속성(Dynamic Properties)

by 사악신 2015. 1. 8.


어려서부터 파스칼을 해온터라 형(Type)에 대해선 습관적으로 까다로운 편입니다. 그런 저를 반나절 넘게 멘붕에 빠지게 한 녀석이 있었으니...


class ProjectPrice extends \Eloquent {

protected $fillable = [];

protected $table = 'project_prices';

protected $primaryKey = 'dpr_id';


public function results()

{

return $this->hasMany('ProjectPriceResult', 'dpr_id');

}


public function getCountAttribute()

{

return $this->results()->count();

}

}


코드를 간략히 해보았습니다만, ProjectPrice 와 ProjectPriceResult 는 1:n 의 관계입니다. 여기서....


return $this->results()->count();

return $this->results->count();


둘 다 같은 결과를 반환합니다. 전자는 메소드 호출 후 체인연결이고 후자는 속성을 읽은 후 체인 연결입니다. 그리고 $this 는 Eloqeuent 를 상속(2015/01/08 - [프로그래밍/언어 - PHP] - Laravel 4 Eloquent 클래스는 어디에?) 받은 고로 Model 의 인스턴스입니다.


일단, Model 의 hasMany() 를 살펴보았습니다. 메소드의 반환하는 형이 HasMany 이더군요. 하지만 HasMany 클래스에는 count() 라는 메소드가 존재하지 않았습니다. 당연히 부모 클래스에 있을 것이라 보고 HasOneOrMany 와 Relation 을 살펴보았지만 역시 count() 메소드는 존재하지 않았습니다.(여기서 멘붕...)


당황하며 Laravel Framework 소스까지 까봤지만 정말 존재하지 않는 메소드이더군요.;; 이후 아는 지인(PHP의 고수)을 통해 PHP 에는 매직 메소드라는 놈이 있다는 것을 알게됐습니다.(그래... 언어별 차이는 항상 있는 거니까.)


$this->results()->count();


1. hasMany() 메소드 호출되며 HasMany 클래스의 인스턴스 반환함.

2. HasMany 클래스의 인스턴스에서 count() 메소드를 호출하지만 해당 메소드가 존재하지 않음.

3. HasMany 의 부모 클래스인 Relation 에 선언된 __call 매직 메소드에 의해 아래 코드가 실행됨.


public function __call($method, $parameters)

{

$result = call_user_func_array(array($this->query, $method), $parameters);


if ($result === $this->query) return $this;


return $result;

}


4. 결과로 빌더(Builder)의 인스턴스가 반환됨.

5. Builder 클래스의 count() 메소드가 호출됨.


$this->results->count();


1. Model 의 인스턴스인 $this 에서 results 속성을 불러오지만 해당 속성이 존재하지 않음.

2. Model 클래스에 선언된 __get 이 호출됨.


public function __get($key)

{

return $this->getAttribute($key);

}



3. getAttribute 메소드는 다시, method_exists() 에 의해 - results() 가 존재하므로... - getRelationshipFromMethod() 메소드를 호출함.


public function getAttribute($key)

{

$inAttributes = array_key_exists($key, $this->attributes);


// If the key references an attribute, we can just go ahead and return the

// plain attribute value from the model. This allows every attribute to

// be dynamically accessed through the _get method without accessors.

if ($inAttributes || $this->hasGetMutator($key))

{

return $this->getAttributeValue($key);

}


// If the key already exists in the relationships array, it just means the

// relationship has already been loaded, so we'll just return it out of

// here because there is no need to query within the relations twice.

if (array_key_exists($key, $this->relations))

{

return $this->relations[$key];

}


// If the "attribute" exists as a method on the model, we will just assume

// it is a relationship and will load and return results from the query

// and hydrate the relationship's value on the "relationships" array.

$camelKey = camel_case($key);


if (method_exists($this, $camelKey))

{

return $this->getRelationshipFromMethod($key, $camelKey);

}

}


4. $relations 는 Relation 클래스의 인스턴스이며, 해당 getResults() 메소드를 호출함.


protected function getRelationshipFromMethod($key, $camelKey)

{

$relations = $this->$camelKey();


if ( ! $relations instanceof Relation)

{

throw new LogicException('Relationship method must return an object of type '

. 'Illuminate\Database\Eloquent\Relations\Relation');

}


return $this->relations[$key] = $relations->getResults();

}


5. Relation 의 getResults() 메소드는 추상 메소드로, 상속 받은 클래스(BelongsTo,BelongToMany, HasMany.. 등등)에서 구현됩니다.

6. HasOne, BelongsTo 와 같은 1:1 관계를 나타내는 클래스의 경우....


public function getResults()

{

return $this->query->first();

}



7. HasMany, BelongsToMany 와 같은 1:n 관계를 나타내는 클래스의 경우...


public function getResults()

{

return $this->get();

}


6의 경우 Builder 의 인스턴스를 반환하며, 7의 경우 Collection 의 인스턴스를 반환합니다.


이것을 매뉴얼에서는 엘로퀀트의 Dynamic Propertis 라고 부르고 있더군요. 1:1 또는 1:n 의 관계에 따라 자동으로 get 또는 first 를 호출한다고...


Eloquent allows you to access your relations via dynamic properties. Eloquent will automatically load the relationship for you, and is even smart enough to know whether to call the get (for one-to-many relationships) or first (for one-to-one relationships) method. It will then be accessible via a dynamic property by the same name as the relation. For example, with the following model $phone


그리고,


echo $phone->user()->first()->email;

echo $phone->user->email;


It may be shortened to simply. 라고 설명해놓았습니다.


제 정신구조상 이게 자동으로 된다고하면, 코딩시 여러 예외 상황에 혼돈을 느끼는 경우가 많아 결국 다 파헤쳐보게된 셈인데요. 정리하면 다음과 같습니다.


1. 1:1 의 경우 Builder 의 인스턴스

2. 1:n 의 경우 Collection 의 인스턴스(속성으로 체인연결)

3. 1:n 의 경우 Builder 의 인스턴스(메소드로 체인연결)


상기 count() 의 결과로는 2 와 3 을 사용할 수 있지만 성능으로는 3의 경우가 빠르지않을까 조심스레 유추해 볼 수 있습니다. ^^


반응형

댓글