Viblo Code
+1

What is Reflection in PHP?

What is Reflection in PHP?

Reflection trong phát triển phần mềm được sử dụng khá thường xuyên.
Reflection là nơi mà một object có thể xem xét lại chính nó và thông báo cho bạn về method và properties của nó trong thời gian chạy (runtime).
Vậy chúng ta có thể sử dụng Reflection trong PHP như thế nào?

Reflection là cái gì và tại sao nó lại hữu dụng?

Reflection, giống như tên của nó, là khả năng cho phép một chương trình máy tính xem xét chính nó và nói cho bạn về các properties, methods và các kiểu của các objects mà bạn đang làm việc. Điều này rất hữu ích trong một số trường hợp hoặc kịch bản khác nhau. Ví dụ đọc code của ai đó mà không biết cái biến này là gì, 1 object hay 1 số, 1 string thì có thể dùng hàm cơ bản của PHP là get_class()get_class_method(). Một cách sử dụng thông dụng thứ hai đó là tạo tài liệu. Thông thường việc tạo tài liệu khá tốn thời gian và công sức, khi mà chúng ta cần viết tài liệu cho mỗi function ở mỗi class của framework hay application lớn. Thay vào đó, Reflection có thể tự động tạo ra tài liệu cho bạn. Bằng việc nó tự động kiểm tra từng method, constructor, class để xác định đầu vào và đầu ra của chúng. Laravel cũng sử dụng rất tốt Reflection để tự động inject các dependencies thông qua IoC container.

<?php
class UsersController {

  public function __construct(User $user)
  {
    $this->user = $user;
  }

}

Laravel biết rằng cần inject một instance của User model. Laravel có thể làm vậy bằng cách sử dụng Reflection

Những PHP Reflection functions thông dụng

Như đề cập ở trên, hai PHP Reflection functions thông dụng nhất đó là get_class()get_class_methods()

<?php
var_dump(get_class($user));
//App\Models\User

var_dump(get_class_method($user));
//Method A
//Method B
//Method C
//Something else

get_class() trả về 1 string tên của class và get_class_method() trả về 1 mảng tên các method trong object đó.

Một hàm khác cũng hay dùng đấy là method_exists():

<?php
class User {

    protected function getUsername()
    {
        //....
    }

    public function __get($param)
    {
        $method = 'get' . ucfirst($param);

        if (method_exists($this, $method)) {
            return $this->{$method}();
        }
    }
}

Hàm này thực hiện kiểm tra sự tồn tại của function trong class User.

Các class PHP Reflection

Thay vì cố gắng ghi nhớ tất cả, chúng ta sẽ xem xét một vài điều chung nhất, bạn có thể sẽ cần khi làm việc với Reflection trong PHP

<?php

namespace App\Models\Facebook;

class UUID
{

}

abstract class Entity
{

}

interface Friendable
{

}

interface Likeable
{

}

interface Postable
{

}

class User extends Entity implements Friendable, Likeable, Postable
{
    public function __construct($name, UUID $uuid)
    {

    }

    public function like(Likebable $entity)
    {

    }

    public function friend(User $user)
    {

    }

    public function post(Post $post)
    {

    }
}

$reflection = new \ReflectionClass(new User('PHP Reflection Ex', new UUID(1234)));

Get the class name

Reflection method đầu tiền chúng ta xem xét đó chính là lấy tên đầy đủ của class:

<?php
echo $reflection->getName();
// Facebook\Entities\User

hoặc chỉ lấy tên ngắn gọn của class:

<?php
echo $reflection->getShortName();
// User

hoặc đơn giản chỉ là muốn lấy namespace của class đó:

<?php
echo $reflection->getNamespaceName();
// Facebook\Entities

Get the parent class

User object có một parent abstract class. Chúng ta có thể lấy được một instance của reflection class thông qua phương thức getParentClass().

<?php
$parent = $reflection->getParentClass();
echo $parent->getName();
// Facebook\Entities\Entity

Get the interfaces

User object được implement từ một số interfaces khác nhau. Chúng ta có thể lấy được tên các interfaces đó qua phương thức getInterfaceNames():

<?php
$interfaces = $reflection->getInterfaceNames();

echo "<pre>";
var_dump($interfaces);
/*
array(3) {
  [0]=>
  string(28) "Facebook\Entities\Friendable"
  [1]=>
  string(26) "Facebook\Entities\Likeable"
  [2]=>
  string(26) "Facebook\Entities\Postable"
}
*/

Bạn cũng có thể lấy được mảng interfaces như ReflectionClass instance.

<?php
$interfaces = $reflection->getInterfaces();

echo "<pre>";
var_dump($interfaces);

/*
array(3) {
  ["Facebook\Entities\Friendable"]=>
  &object(ReflectionClass)#3 (1) {
    ["name"]=>
    string(28) "Facebook\Entities\Friendable"
  }
  ["Facebook\Entities\Likeable"]=>
  &object(ReflectionClass)#4 (1) {
    ["name"]=>
    string(26) "Facebook\Entities\Likeable"
  }
  ["Facebook\Entities\Postable"]=>
  &object(ReflectionClass)#5 (1) {
    ["name"]=>
    string(26) "Facebook\Entities\Postable"
  }
}
*/

Get the class methods

<?php
$methods = $reflection->getMethods();
var_dump($methods);

echo "<pre>";
var_dump($methods);
/*
array(3) {
  [0]=>
  &object(ReflectionMethod)#2 (2) {
    ["name"]=>
    string(4) "like"
    ["class"]=>
    string(22) "Facebook\Entities\User"
  }
  [1]=>
  &object(ReflectionMethod)#3 (2) {
    ["name"]=>
    string(6) "friend"
    ["class"]=>
    string(22) "Facebook\Entities\User"
  }
  [2]=>
  &object(ReflectionMethod)#4 (2) {
    ["name"]=>
    string(4) "post"
    ["class"]=>
    string(22) "Facebook\Entities\User"
  }
}
*/

Get the Constructor

<?php
$constructor = $reflection->getConstructor();

echo "<pre>";
var_dump($constructor);
/*
object(ReflectionMethod)#2 (2) {
  ["name"]=>
  string(11) "__construct"
  ["class"]=>
  string(22) "Facebook\Entities\User"
}
*/

Lúc này chúng ta có thể lấy được các dependencies của class:

<?php
echo "<pre>";
var_dump($constructor->getParameters());
/*
array(2) {
  [0]=>
  &object(ReflectionParameter)#3 (1) {
    ["name"]=>
    string(4) "name"
  }
  [1]=>
  &object(ReflectionParameter)#4 (1) {
    ["name"]=>
    string(4) "uuid"
  }
}
*/

Tiếp đó có thể sử dụng Reflection để lấy các instance của dependencies objects:

<?php
$parameters = $constructor->getParameters();

echo "<pre>";
var_dump($parameters[1]->getClass());
/*
object(ReflectionClass)#5 (1) {
  ["name"]=>
  string(22) "Facebook\Entities\UUID"
}
*/

Laravel Reflection làm việc như thế nào?

Trở lại với câu hỏi: "Làm thế nào mà Laravel tự động inject đúng các dependencies vào trong một class?" Laravel có thể tự động giải quyết các dependencies của bạn và inject chúng vào trong class bằng cách sử dụng PHP ReflectionClass. Xét về mặt ngữ nghĩa thì có vẻ như điều trên giống như ma thuật vậy bởi vì với việc sử dụng PHP sẽ yêu cầu bạn làm các công việc inject dependencies bằng tay.

Tuy nhiên, phương thức nằm dưới sự ảo diệu đó thật ra chỉ là ứng dụng của PHP Reflection.

Vậy Laravel IoC container giải quyết dependencies như thế nào?

Một trong những method quan trọng nhất của IoC container là build() method. Đầu tiên build() method tạo một ReflectionClass instance:

<?php
$reflector = new ReflectionClass($concrete);

Sử dụng khả năng của ReflectionClass, đầu tiên nó kiểm tra xem class có instantiable không:

<?php
// If the type is not instantiable, the developer is attempting to resolve
// an abstract type such as an Interface of Abstract Class and there is
// no binding registered for the abstractions so we need to bail out.
if ( ! $reflector->isInstantiable()) {
    $message = "Target [$concrete] is not instantiable.";

    throw new BindingResolutionException($message);
}

nếu class chưa được inject dependencies thông qua constructor. IoC container có thể trả về một instance mới của object:

<?php
$constructor = $reflector->getConstructor();
// If there are no constructors, that means there are no dependencies then
// we can just resolve the instances of the objects right away, without
// resolving any other types or dependencies out of these containers.
if (is_null($constructor)) {
    return new $concrete;
}

Nếu class không có constructor, IoC container sẽ lấy các parameters đang được injected, và giải quyết các instances của một lần sử dụng ReflectionParameter:

<?php
$dependencies = $constructor->getParameters();

// Once we have all the constructor's parameters we can create each of the
// dependency instances and then use the Reflection instances to make a
// new instance of this class, injecting the created dependencies in.
$parameters = $this->keyParametersByArgument(
    $dependencies, $parameters
);

$instances = $this->getDependencies(
    $dependencies, $parameters
);

return $reflector->newInstanceArgs($instances);

Qua bài viết nay hy vọng các bạn có thể hiểu thêm về PHP Reflection và ứng dụng của nó.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.