بایگانی دسته: reflection

ماکرو ها در لاراول macro in laravel

ماکرو در واقع امکانی هست که متوانیم رفتار یک کلاس را در زمان اجرا تغییر دهیم . با اضافه کردن یک متد به کلاس.
یه مثال :

در فایل AppServiceProvider در متد بوت کد زیر را مینویسیم.

    public function boot()
    {
        Response::macro('hi', function ($name) {
            return 'Hi ' . $name ;
        });
    }

و الان می میتوانیم متد تعریف شده را روی کلای Response صدا بزنیم.
در فایل web کد زیر را مینویسیم .

use Illuminate\Support\Facades\Response;
Route::get('macro', function () {
    echo Response::hi('Ali');
    echo ' ';
    echo response()->hi('Ali');
});

خروجی

//localhost:8000/macro
Hi Ali Hi Ali

مثال های بیشتر از راهنمای لاراول
https://laravel.com/docs/8.x/collections#extending-collections
https://laravel.com/docs/8.x/responses#response-macros
https://laravel.com/docs/8.x/scout#builder-macros
سوال پیش میاد چه نیازی هست به این کار؟
پاسخی که من به ذهنم میرسه، اگه یه کار تکراری رو توی یه کلاس انجام میدیم میتونیم اون رو یک بار به عنوان ماکرو به کلاس اضافه کنیم و بعدا بارها از اون استفاده کنیم، و کمک میکنه کد کمتر بنویسیم و کد کمتر هم یعنی باگ کمتر. اگه از ماکرو استفاده نکنیم از چی می‌تونیم استفاده کنیم؟
از ارث بری :
از تریت ها :
از متدهای کمکی(helpers) :
همه اینها کمک میکنن کد کمتر بنویسیم اما خوبی ماکرو اینه که میتونیم رفتار کلاس های موجود رو در زمان اجرا runtime تغییر بدیم.
آیا میتوانیم همه کلاسها را تغییر دهیم ؟‌
خیر، فقط کلاسهایی که Macroable هستند.

با جستجوی Macroable در کد لاراول کلاس های پیش فرض که در لاراول به صورت ماکروابیل تعریف شده اند،‌را میتوانید مشاهده کنید.  (یک لیست هم اینجا هست)

یک کلاس ماکروابیل تعریف کنیم.

<?php

namespace App\Services;

use \Illuminate\Support\Traits\Macroable;

class Person {
    use Macroable;

    private $name;

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

    public function getName() {
        return $this->name;
    }
}

و در متد بوت پروایدر یک ماکرو ساده تعریف میکنیم.

class <a href="https://github.com/spatie/macroable">AppServiceProvider</a> extends ServiceProvider
{
    public function boot()
    {
        \App\Services\Person::macro('hi', function() {
            return 'Hi ' . $this->getName();
        });
    }
}

و آن متد را روی کلاس اجرا میکنیم.

// http://localhost:8000/person
Route::get('person', function () {
    $person = new \App\Services\Person('Ali');
    echo $person->hi(); // use macro
    // Hi Ali
});

می بینیم که به راحتی میتوانیم کلاس ها را ماکروابل تعریف کنیم و آنها را گسترش دهیم. با نگاهی به تریت ماکروابیل،  میبینیم که از reflection ها استفاده میکند.

میتوانیم برای خودمان متد های پرکاربرد را ذخیره کنیم و دفعه بعد به صورت ماکرو آنها را تعریف کنیم. 🙂
البته حواسمون هست که ماکرو رو میتونیم در سرویس پروایدرهای دیگری غیر از <a href="https://github.com/spatie/macroable">AppServiceProvider</a> تعریف کنیم، و اسم های خوانا تری هم برای آنها انتخاب کنیم.
یک پکیج نمونه که قابلیت های لاراول رو با ماکرو ها توسعه داده
https://github.com/spatie/macroable

فایل ها

رفلکشن در پی اچ پی Reflection in PHP بخش دوم

در قسمت قبل دیدیم که با رفلکشن ReflectionFunction می‌توانیم اجزای یک تابع را بررسی کنیم و پارامتر‌های تابع هم از نوع رفلکشن ReflectionParameter هستند و میتوان اجزای آنها را هم بررسی کرد. در این بخش یک کلاس تعریف میکنیم و آن را بررسی میکنیم.
ابتدا یک کلاس به نام Person را تعریف میکنیم

<?php
 /** * This is simple description about class * */
 class Person 
{
 public $name;
 private $age;

 public function __construct()
   {
      $this->name = 'Anonymous';
   }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setAge($age)
    {
        $this->age = $age;
    }

    public function getAge()
    {
        return $this->age;
    }
}

$rc = new ReflectionClass('Person');

echo $rc->getName(); // Person  
// نام کلاس را بر میگرداند

// echo $rc->getDocComment();
// /**
// * This is simple description about  class 
// *
// */

// توضیحات بالای کلاس را برمیگرداند 

$methods = $rc->getMethods();
// print_r($methods); 
//   هستند ReflectionMethod لیستی از متد های تابع را برمیگرداند که از نوع 
foreach ($methods as $method) {
    echo $method->getName(); 
    //    دقت میکنید که به یک ابجکت از نوع رفلکشن متد دسترسی داریم و از توابع مربط به خودش میتوانیم استفاده کنیم 
    echo "\n";
}

همان گونه که در کد بالا می بینبد می توانیم اجزاء کلاس و ساختار هر بخش آن را بررسی کنیم و نام کلاس getName و با استفاده از متد getMethods متد های کلاس را ببینیم که این متد برای یک آرایه از نوع ReflectionMethod بر می‌گرداند.
ReflectionMethodساختاری شبیه به ReflectionFunction دارد و  هر دو اینها از کلاس ReflectionFunctionAbstract ارث بری کرده اند

ReflectionFunctionAbstract implements Reflector
ReflectionFunction extends ReflectionFunctionAbstract implements Reflector
ReflectionMethod extends ReflectionFunctionAbstract implements Reflector

کلاس ReflectionFunctionAbstract شامل متد های زیر است

final private __clone ( void ) : void
public getClosureScopeClass ( void ) : ReflectionClass
public getClosureThis ( void ) : object
public getDocComment ( void ) : string
public getEndLine ( void ) : int
public getExtension ( void ) : ReflectionExtension
public getExtensionName ( void ) : string
public getFileName ( void ) : string
public getName ( void ) : string
public getNamespaceName ( void ) : string
public getNumberOfParameters ( void ) : int
public getNumberOfRequiredParameters ( void ) : int
public getParameters ( void ) : array
public getReturnType ( void ) : ReflectionType
public getShortName ( void ) : string
public getStartLine ( void ) : int
public getStaticVariables ( void ) : array
public hasReturnType ( void ) : bool
public inNamespace ( void ) : bool
public isClosure ( void ) : bool
public isDeprecated ( void ) : bool
public isGenerator ( void ) : bool
public isInternal ( void ) : bool
public isUserDefined ( void ) : bool
public isVariadic ( void ) : bool
public returnsReference ( void ) : bool
abstract public __toString ( void ) : void

ReflectionFunction متد های بالا را دارد اما ReflectionMethod متد های زیر  را علاوه بر متد های بالا دارد

public static export ( string $class , string $name [, bool $return = FALSE ] ) : string
public getClosure ( object $object ) : Closure
public getDeclaringClass ( void ) : ReflectionClass
public getModifiers ( void ) : int
public getPrototype ( void ) : ReflectionMethod
public invoke ( object $object [, mixed $... ] ) : mixed
public invokeArgs ( object $object , array $args ) : mixed
public isAbstract ( void ) : bool
public isConstructor ( void ) : bool
public isDestructor ( void ) : bool
public isFinal ( void ) : bool
public isPrivate ( void ) : bool
public isProtected ( void ) : bool
public isPublic ( void ) : bool
public isStatic ( void ) : bool
public setAccessible ( bool $accessible ) : void

با نگاهی متد های اضافه می بینم چه ویژگی هایی یک متد را از فانکشن متمایز میکند.

ایده :‌ به لیست متد ها نگاه کنید  و فکر کنید هر کدام چه کاربردی دارند یا اینکه کدام با کاربرد کدام آشنا نیستید.

به متد setAccessible دقت کنید، آیا  واقعا میتوانیم دسترسی یک متد را تغییر دهیم ؟ (بررسی می‌کنیم)

یک کلاس تعریف میکنیم که یک متد private هم داشته باشد.

<?php

class Math 
{
    public static function power($num)
    {
            return "Power {$num} is " . self::calcPower($num);
    }

    private static function calcPower($num)
    {
        return $num * $num;
    }
}

echo Math::power(2);
// Power 2 is 4

// echo Math::calcPower(2);
// PHP Fatal error:  Uncaught Error: Call to private method Math::calcPower() from context ''

همان طور که انتظار داریم متد private را نمی توانیم خارج از خود کلاس صدا بزنیم(دسترسی نداریم)، اما با استفاده از رفلکشن می توانیم این کار را انجام دهیم

$rm = new ReflectionMethod('Math', 'calcPower');

$rm->setAccessible(true);

echo  $rm->invoke(null, 2); // صدا زدن متد استاتیک
// ۴

با استفاده از setAccessible و invoke توانستیم یک متد private  را خارج از کلاس صدا بزنیم. (جالب بود نه)

برای پروپرتی هم ReflectionProperty داریم و می توانیم دسترسی ها را تغییر دهیم و از آنها هم استفاده کنیم.

در پایین لیست رفلکشن ها را و کلاس ها و اینترفیس های مربوط به رفلکشن‌ها را میبینیم که می توانیم یک دید کلی در مورد آنها پیدا کنیم.

Reflector 
 ReflectionClassConstant implements Reflector
 ReflectionProperty implements Reflector
 ReflectionClass implements Reflector
 ReflectionObject extends ReflectionClass implements Reflector

 ReflectionZendExtension implements Reflector
 ReflectionExtension implements Reflector 

 ReflectionFunctionAbstract implements Reflector 
 ReflectionFunction extends ReflectionFunctionAbstract implements Reflector 
 ReflectionMethod extends ReflectionFunctionAbstract implements Reflector
 
 ReflectionType 
 ReflectionNamedType extends ReflectionType

 ReflectionParameter implements Reflector 
 
 ReflectionGenerator 
 Reflection
 ReflectionException extends Exception

احتمالا بررسی ReflectionObject هم جالب خواهد بود. (تمرین)

فایل های این بخش

در بخش بعد در مورد کاربرد رفلکش‌ها می نویسم و بحث رفلکشن را تمام میکنیم.

 

رفلکشن در پی اچ پی Reflection in PHP

رفلکشن قابلیتی در یک برنامه کامپیوتر است که میتوان ساختار و رفتار خود برنامه در زمان اجرا را بررسی کرد و تغییر داد.
طبق تعریف php.net : رفلکشن به ما قابلیت مهندسی معکوس کلاس ها، اینترفیس ها، توابع، متد ها و اکستنشن ها را میدهد به این معنی که بدانیم چه قابلیت هایی دارد. بعلاوه میتوان برای دریافت راهنمای (doc comments) کلاس ها، متدها و فانکشن ها از رفلکشن استفاده کرد.
این قابلیت از نسخه ۵.۶ به php اضافه شده است.

برای شروع چند دستور را که با این قابلیت در php  وجود دارد را بررسی میکنیم

$ php --rf strlen اطلاعات تابع
$ php --rc finfo اطلاعات کلاس
$ php --re json اطلاعات اکستنشن
$ php --ri dom اطلاعات کانفیگ اکستنشن

با اجرای php –rf strlen

Function [ <internal:Core> function strlen ] {
   - Parameters [1] {
         Parameter #0 [ <required> $str ]
    }
}

اطلاعاتی که به ما میده این است که تابع مربوط به هسته اصلی php  است و تعداد پارامتر های آن یکی است و آن پارامتر هم اجباری است. برای مقایسه میتوان خروجی های توابع دیگر را هم دید.

php --rf json_encode
php --rf sprintf

و همچنین با با دستورات بالا میتوان اطلاعات کاملی در مورد تابع، کلاس، اکستنش و کانفیگ اکستنشن دریافت کرد. یعنی میتوان اطلاعاتی در مورد کلاس به دست آورد

php --rc DateTime
Class [  class DateTime implements DateTimeInterface ] {

  - Constants [13] {
    Constant [ public string ATOM ] { Y-m-d\TH:i:sP }
    ...
  }

  - Static properties [0] {
  }

  - Static methods [3] {
    Method [  static public method __set_state ] {
    }
    ...
  }

  - Properties [0] {
  }

  - Methods [15] {
    Method [ <internal:date, ctor> public method __construct ] {

      - Parameters [2] {
        Parameter #0 [ $time ]
        Parameter #1 [ $timezone ]
      }
    }
    ...
  }
}

اطلاعات کاملی از کلاس DateTime به ما میدهد از تعداد ثابت ها و پروپرتی ها و متد های استاتیک و سایر متد ها و برای هر متد و پروپرتی هم اطلاعات کلی از آن به ما می دهد.

برای اطلاعات اکستنشن و کانفیگ اکستنشن ها هم می توانیم از دستورات بالا استفاده کنیم و جزئیات آنها را بررسی کنیم.

خوب حالا برای کلاس ها و توابعی که خودمون تعریف کردیم باید چه کنیم ؟ (با یه پرش بلند خودمون رو به اعماق رفلکشن می رسونیم 🙂 )

ابتدا یک تابع می نویسیم

<?php
 /**
 * Echo bar
 */
 function hello($name = 'World') : string
 {
    return 'Hello ' . $name . "\n";
 } 
// echo hello();

 $rf = new \ReflectionFunction('hello');
 echo $rf->getName(); 
// hello
echo $rf->getStartLine();
// ۶ 
echo $rf->getEndLine();
// ۹
echo $rf->getFileName();
// /home/user/Desktop/Reflection/function.php

echo $rf->getShortName(); 
// hello

echo $rf->getNamespaceName(); 
// hello

echo $rf->getDocComment(); 
// /**
//  * Echo  bar
//  */

echo $rf->getReturnType();
// string

print_r($rf->getParameters());
/*
    (
        [۰] => ReflectionParameter Object
            (
                [name] => name
            )
    )
*/

با استفاده از کلاس ReflectionFunction توانستیم کلی اطلاعات از یک فانکشن دریافت کنیم. از نام تابع، تا لاین شروع و پایان و فایلی که در آن وجود دارد و return type و … . حتی توانستیم کامنتی که خودمان نوشته بودیم هم دریافت کنیم.
و با کمی کنجکاوی می توانید اطلاعات بامزه دیگری هم با خواندن راهنمای کلاس ReflectionFunction پیدا کنید.

کاربرد :  اگر از لاراول استفاده میکنید و دوست دارید بدانید تابع dd یا config در کدام فایل تعریف شده اند می تواند از این رفلکشن استفاده کنید.

به خروجی پارامتر ها دقت میکنید، یک پارامتر از نوع ReflectionParameter دارد و نام آن name است و خود یک آبجکت است که می توانیم خود پارامتر را هم دوباره بررسی کنیم (با رفلکشن مهندسی معکوس انجام بدیم). جالبه نه می توانیم ببینیم یک پارامتر فانکشن در php چه ویژگی هایی دارد 🙂 . و به همین صورت برای کلاس و ثابت ها و پروپرتی ها، نوع ها و ابجکت و … هم رفلکشن داریم.

ایده : با کنجکاوی کردن در اعماق رفلکشن ها می توانیم بفهمیم چقدر ساختار OOP  در PHP  را مسلط هستیم و با مفاهیم جالبی برخورد میکنیم که ارزش وقت گذاشتن دارد.

کلاس های تعریف شده برای رفلکشن کلاس ها، متد ها و …

Reflector
ReflectionClassConstant implements Reflector
ReflectionProperty implements Reflector
ReflectionClass implements Reflector
ReflectionObject extends ReflectionClass implements Reflector

ReflectionZendExtension implements Reflector
ReflectionExtension implements Reflector

ReflectionFunctionAbstract implements Reflector
ReflectionFunction extends ReflectionFunctionAbstract implements Reflector
ReflectionMethod extends ReflectionFunctionAbstract implements Reflector

ReflectionType
ReflectionNamedType extends ReflectionType

ReflectionParameter implements Reflector

ReflectionGenerator
Reflection
ReflectionException extends Exception

در پست بعدی در مورد ReflectionClass  و ReflectionObject این بحث را ادامه می دهیم.

فایل های این بخش

منبع و مطاله بیشتر https://www.php.net/manual/en/book.reflection.php