jclaveau / php-deferred-callchain
Register a chain of calls that will be applied on an instance later
Installs: 6 960
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 3
Forks: 0
Open Issues: 3
Requires
- php: >=5.6.0
- jclaveau/php-fluent-trait: ^1.1
- jclaveau/php-visibility-violator: ^1.0
- symfony/polyfill-php72: ^1.12
Requires (Dev)
- phpunit/phpunit: >=5
README
This class simply provides a way to define fluent chain of methods, functions, array access calls before having the target (object or native value) you wan't to apply it to. Once the expected targets are available, simply call the chain on them as if it was a function.
Quality
Overview
// having class MyClass { protected $name = 'unnamed'; public function setName($name) { $this->name = $name; return $this; } public function nameEntries() { return [ 'my_entry_1' => $this->name . " 1", 'my_entry_2' => $this->name . " 2", ]; } } // We can define some chained calls (none is executed) $doDummyCallsLater = DeferredCallChain::new_( MyClass::class ) // Targets must be MyClass instances ->nameEntries()['my_entry_2'] // access array entry ->strtoupper() // apply strtoupper() to it ; // do whatever we want // ... // Get your targets $myInstance1 = (new MyClass)->setName('foo'); $myInstance2 = (new MyClass)->setName('bar'); // Execute the callchain echo $doDummyCallsLater( $myInstance1 ); // => FOO 2 echo $doDummyCallsLater( $myInstance2 ); // => BAR 2
Installation
php-deferred-callchain is installable via Composer
composer require jclaveau/php-deferred-callchain
Testing
Tests are located here and runnable by calling
./phpunit
Usage
- Functionnal and chained construction
- Fluent call chain
- Working with arrays
- Working with native types and functions
- Specifying on which class, interface, type or instance, the chain is callable
- Calls provoking exceptions
- Static calls
- API Reference
Functionnal and chained construction
DeferredCallChain can be instanciated classically
$nameRobert = (new DeferredCallChain(Human::class))->...
Statically
$nameRobert = DeferredCallChain::new_(Human::class)->...
Or functionnaly
$nameRobert = later(Human::class)->...
Fluent call chain
$nameRobert = DeferredCallChain::new_() ->setName('Muda') ->setFirstName('Robert') ; $mySubjectIMissedBefore = new Human; $robert = $nameRobert( $mySubjectIMissedBefore ); echo $robert->getFullName(); // => "Robert Muda" echo (string) $nameRobert; // => "(new JClaveau\Async\DeferredCallChain)->setName('Muda')->setFirstName('Robert')"
Working with arrays
$getSubColumnValue = DeferredCallChain::new_() ['column_1'] ['sub_column_3'] ; $sub_column_3_value = $getSubColumnValue( [ 'column_1' => [ 'sub_column_1' => 'lalala', 'sub_column_2' => 'lololo', 'sub_column_3' => 'lilili', ], 'column_2' => [ 'sub_column_1' => 'lululu', 'sub_column_2' => 'lelele', 'sub_column_3' => 'lylyly', ], ] ); echo $sub_column_3_value; // => "lilili" echo (string) $getSubColumnValue; // => "(new JClaveau\Async\DeferredCallChain)['column_1']['sub_column_3']"
Working with native types and functions
The features above make calls to objects methods easy and async but when their result is not an object, the fluent syntax has to stop, and the async behavior also.
Based on Daniel S Deboer work https://github.com/danielsdeboer/pipe, support of chained function calls has been added.
class MyClass { public function getString() { return 'string'; } } $upperifyMyClassString = DeferredCallChain::new_( MyClass::class ) ->getString() ->strtoupper(); echo $upperifyMyClassString( new MyClass ); // prints "STRING"
Some functions do not use the subject of the fluent syntax as first argument. In this case, giving '$$' as the parameter you want to be replaced by the subject.
class MyClass { public function getSentence() { return 'such a funny lib to implement'; } } $explodeMyClassSentence = DeferredCallChain::new_( MyClass::class ) ->getSentence() ->explode(' ', '$$'); $explodeMyClassSentence( new MyClass ); // returns ['such', 'a', 'funny', 'lib', 'to', 'implement']
Specifying on which class, interface, type or instance, the chain is callable
You can force the target of your call chain to:
- be an instance of a specific class
$nameRobert = DeferredCallChain::new_(Alien::class) ->setName('Muda') ->setFirstName('Robert') ; $mySubjectIMissedBefore = new Human; $robert = $nameRobert( $mySubjectIMissedBefore ); // throws BadTargetClassException
- implement a specific interface
$getCount = DeferredCallChain::new_("\Traversable") ->count() ; $myCountableIMissedBefore = new CountableClass; // class implementing Countable // throws BadTargetInterfaceException
- be of a specific native type
$nameRobert = DeferredCallChain::new_("array") ->setName('Muda') ->setFirstName('Robert') ; $mySubjectIMissedBefore = new Human; $robert = $nameRobert( $mySubjectIMissedBefore ); // throws BadTargetTypeException
- be a specific instance given at construction
$myTarget = new Human; $nameRobert = DeferredCallChain::new_($myTarget) ->setName('Muda') ->setFirstName('Robert') ; $robert = $nameRobert( new Human ); // throws TargetAlreadyDefinedException
Calls provoking exceptions
As a call can be made far before it's effectivelly applied, exceptions need more debug information for a smooth workflow. To achieve that, a line is added to every exception message thrown during a DeferredCallChain execution, pointing to the buggy call and where it is coded.
For example, an exception having as message An exception has been thrown by some user code
will print
An exception has been thrown by some user code
When applying (new JClaveau\Async\DeferredCallChain( <instance id> ))->previousSuccessfullCall()->buggyCall('Robert') defined at <file>:<line>
Static calls
Static calls can be useful, especially for singletons. For some technical reasons explained here, the only way to support it is to call them as normal methods (e.g. with -> ) and look for it as a static method once we know it doesn't exist as a regular one.
later(MyModel::class)->getInstance()->myNormalGetter(); // or later($myModel)->getInstance()->myNormalGetter(); // like $myModel::getInstance()