Sucker (присоска) — PHP компонент для теста приватных методов и свойств
В рамках хобби пишу свои собственные компоненты.
Но есть проблема - отсутствие аудитории.
Чтобы полноценно тестировать компоненты на работоспособность, решил через Хабр дать популярность некоторым своим компонентам.
На днях дописал компонент который универсально может взаимодействовать с приватными свойствами и методами классов и объектов. Цель такого доступа - тестировать работоспособность скрытого кода.
alpa/tools_sucker - https://packagist.org/packages/alpa/tools_sucker
https://github.com/alexeyp0708/php_tools_sucker
Возможно вы зададитесь вопросом - тема стара как мир, зачем еще один схожий компонент?
Например есть легковесная в несколько строк spatie/invade - https://packagist.org/packages/spatie/invade
Да и "на коленке" можно написать свою реализацию.
НО..., согласитесь, всегда охота иметь под рукой легко понятный, прозрачный и универсальный инструмент.
Но хз. Мне нравится писать свои тяжеловесные компоненты.
Ознакомившись, вы поймете насколько прост и эффективен данный компонент.
Я дам обзор кратко. Если проявится интерес то обязательно оцените на гитхабе.
Установка и классы взаимодействия
Установка
composer require alpa/tools_sucker:1.0.*
Использование
Есть три рабочих класса взаимодействия с приватными членами объекта/класса. Но для обзора я опишу два класса:
\Alpa\Tools\Sucker\Sucker - использует явные геттеры и сеттеры для взаимодействия с объектом или классом.
Alpa\Tools\Sucker\Proxy - прокси объект, который использует магию для доступа к объектам и свойствам объекта или класса.
Для примера создадим два подопытных класса
<?php
class A{
private $private_prop='hello';
private static $static_private_prop='hello';
private function private_method($arg)
{
return $arg;
}
private function & private_methodByReference(&$arg=null)
{
return $arg;
}
}
class B extends A{
private $private_prop='bay';
private static $static_private_prop='bay';
private function private_method($arg)
{
return strtoupper($arg);
}
}
Sucker класс
Доступ к приватным членам объекта
<?php
use A;
use B;
use Alpa\Tools\Sucker\Sucker;
// доступ к приватным членам обьекта.
$sucker=new Sucker(new B);
// получаем доступ к приватному совйтсву класса B
echo $sucker->get('private_prop');// bay
echo "\n";
// получаем доступ к приватному совйтсву родительского класса A
echo $sucker(A::class)->get('private_prop');// hello
echo "\n";
echo $sucker->get('private_prop');// bay
echo "\n";
//get by reference
$var = & $sucker(A::class)->get('private_prop');// $var = & A::$private_prop ==='hello'
Доступ к приватным статическим членам класса
<?php
use A;
use B;
use Alpa\Tools\Sucker\Sucker;
// доступ к приватным статическим членам класса.
$sucker=new Sucker(B::class);
echo $sucker->get('static_private_prop');// bay
echo "\n";
echo $sucker(A::class)->get('static_private_prop');// hello
echo "\n";
// Warn : The Scope is automatically reset after calling methods that are responsible for accessing members of the observable object
echo $sucker->get('static_private_prop');// bay
echo "\n";
//get by reference
$var = & $sucker(A::class)->get('static_private_prop');// $var = & A::$static_private_prop ==='hello'
API Sгcker класса
<?php
use Alpa\Tools\Sucker\Sucker;
$sucker = new Socker($object); // for members object
// or
$sucker=new Sucker(Target::class); // for static members class
// при смене области видимости, обьект один и тот же (а не создается новый)
$sucker===$sucker(A::class);
// сброc область видимости
$sucker(null);
/* Внимание: область видимости сохраняется до первого вызова метода API
и после сбрасывается на область видимости по умолчанию (класс обьекта).*/
/* & - указывает что аргументы нужно передавать по ссылке (в аргументах указан только для наглядности),
или можно получать результат по ссылке. */
// запросить значение свойства обькта/класса (можно по сыслке)
& $sucker->get( $prop ); & $sucker( SCOPE::class )->get( $prop );
// установить значение для свойства обькта/класса
$sucker->set( $prop, $value ); $sucker(SCOPE::class)->set( $prop, $value );
// установить значение для свойства обькта/класса по ссылке
$sucker->setRef( $prop, & $value ); $socker(SCOPE::class)->setRef( $prop, & $value );
// проверить наличие свойства обькта/класса
$sucker->isset( $prop ); $sucker(SCOPE::class)->isset( $prop );
// удалить свойство из обьекта. для статического свойства класса будет вызвана стандартная ошибка.
$sucker->unset( $prop ); $sucker(SCOPE::class)->unset( $prop );
// перебрать свойства объекта/класса.
/* in each closure => self::class===SCOPE::class and opening $this */
$sucker->each(function ( $key, & $value ){return true;/* break*/}); $sucker( SCOPE::class )->each( function ($key, & $value){return true;/*break*/} );
// вызвать метод объекта/класса.
& $sucker->call( $method, ...$args ); & $sucker( SCOPE::class )->call( $prop ,...$args );
// вызвать метод объекта/класса с возможностьбю передачи аргументов по ссылке.
& $sucker->apply( $method, [& $arg,...] ); & $sucker( SCOPE::class )->apply( $method, [& $arg,...] );
// Песочница для обработки обьекта по своему усмотрению
/* in sandbox closure => self::class===SCOPE::class and opening $this */
& $sucker->sandbox( function & (& $arg){},[ & $arg,...] ); & $sucker( SCOPE::class )->sandbox( function & (& $arg){},[ & $arg,...] );
Proxy класс
Объект прокси класса позволяет работать, так как будто вы имеете контакт с самим объектом.
<?php
use A;
use B;
use Alpa\Tools\Sucker\Proxy;
$proxy=new Proxy(new B);
echo $proxy->private_prop;// bay
echo $proxy(A::class)->private_prop;// hello
echo $proxy->private_prop;// bay
// доступ к статическим свойствам класса.
$proxy=new Proxy(B::class);
echo $proxy->static_private_prop;// bay
echo $proxy(A::class)->static_private_prop;// hello
echo $proxy->static_private_prop;// bay
// get by reference
$var = & $proxy->static_private_prop;
API Proxy класса
<?php
use Alpa\Tools\Sucker\Proxy;
$proxy = new Proxy($object); // for members object
// or
$proxy=new Sucker(Target::class); // for static members class
// при смене области видимости, обьект один и тот же (а не создается новый)
$proxy===$proxy(A::class);
// сброc область видимости
$proxy(null);
/* Внимание: область видимости сохраняется до первого вызова метода API
и после сбрасывается на область видимости по умолчанию (класс обьекта).*/
// запросить значение свойства обькта/класса (можно по сыслке)
& $proxy->prop; & $proxy(SCOPE::class)->prop;
// установить значение для свойства обькта/класса
$proxy->prop=$vslue; $proxy(SCOPE::class)->prop=$value;
// проверить наличие свойства обькта/класса
isset($proxy->prop); isset($proxy(SCOPE::class)->prop);
// удалить свойство из обьекта. для статического свойства класса будет вызвана стандартная ошибка.
unset($proxy->prop); unset($proxy(SCOPE::class)->prop);
// перебрать свойства объекта/класса.
foreach ($proxy as $key=>$value){
}
foreach ($proxy(SCOPE::class) as $key=>$value){
}
// вызвать метод объекта/класса.
& $proxy->method(...$args); & $proxy(SCOPE::class)->method(...$args);
/* Песочница для обработки обьекта по своему усмотрению.
Аргументы можно передавать по ссылке, а также результат получать по ссылке*.
/* in sandbox closure => self::class===SCOPE::class and opening $this */
& $proxy(function & (...$args){},[& $arg,...]); & $proxy(SCOPE::class)(function & (...$args){},[ & $arg,...]);
Удобно? Практично? - решать вам.
Сильно не критикуйте. Писать код это вам не "хухры мухры".
Всем спасибо. Ставьте звездочку на гитхабе.