Magic function for auto-invoke in php - php

In my Class I have methods with the same prefix at the beginning.
sendMessage()
sendPhoto()
sendDocument()
So what I need, is just somehow initialize another method in the class every time when these methods (with prefix) are initialized without putting anything in methods' body.
Is there any way to do this out of the box? Some magic php function which triggers every time when method with prefix is invoked...

Use __call() magic method:
class MagickClass {
public function __call($name, params = []) {
if (strpos($name, 'send') === 0) {
$this->process(strtolower(str_replace('send', '', $name)));
} else {
throw new Exception('Not found', 404);
}
}
private function process($action) {
}
}

Related

Can't call parent function inside __call() in Laravel

This is my code below.
class MyModel extends Model
{
public function __call($method, $parameters = null) {
if($method == 'create'){
return parent::create($parameters[0]);
if(!$created) {
throw new \App\Exceptions\EloquentException;
}else{
return $created;
}
}
}
}
The problem is that when I call update function of MyModel class instance from postman, something bad happens. It gets stuck and I have to restart my computer each time. So what may the problem be?
I'll try to assume you're simply trying to have a common handler for the create function, that is, to throw an EloquentException in case the create returns a null or false.
If that's the case, you have an excess return statement on line above the if statement, and you should assign the return value of the parent's create method to a variable $created that you use later. You may also remove the else part as code below throw is never going to be executed if the exception is thrown.
class MyModel extends Model
{
public function __call($method, $parameters = null)
{
if ($method == 'create') {
$create = parent::create($parameters[0]);
if (!$created) {
throw new \App\Exceptions\EloquentException;
}
return $created;
}
}
}
It would be better if you could elaborate on the task you're trying to achieve, I feel you're doing it the wrong way.

Generic method to catch and handle exceptions thrown in child classes

Assuming I have a parent class:
class Parent {
//...
}
and a child class with methods:
class Child extends Parent {
public function foo() {
// process and return data
}
public function bar() {
// process and return data
}
// way more methods...
}
then is there a generic way in PHP to handle any exceptions thrown in the child methods? For example, a handler in the Parent class? Or do I need to wrap all the methods bodies in a separate try-catch block?
What I want to achieve, is to return an empty array() if any of the child methods throw any kind of exception.
Yes, this is possible. Well, a parent cannot know all possible child methods, but it can know when an undefined method is called by implementing the __call magic method.
We can use this method to create a try-catch "wrapper" dynamically.
Add this method to your parent:
public function __call($method, $args)
{
// If method call ends with 'Safe'
$isSafeMethod = substr($method, -strlen('Safe')) === 'Safe';
if (!$isSafeMethod) {
trigger_error('Call to undefined method '.__CLASS__.'::'.$method.'()', E_USER_ERROR);
return null;
}
// Strip 'Safe' suffix from method name
$wrappedMethodName = substr($method, 0, strpos($method, 'Safe'));
try {
return $this->$wrappedMethodName($args);
} catch (Exception $e) {
return [];
}
}
Now, anytime you want to invoke this try-catch wrapper, just append "Safe" to the method name that you want to wrap. Full code + example:
class TestParent {
public function __call($method, $args)
{
// If method call ends with 'Safe'
$isSafeMethod = substr($method, -strlen('Safe')) === 'Safe';
if (!$isSafeMethod) {
trigger_error('Call to undefined method '.__CLASS__.'::'.$method.'()', E_USER_ERROR);
return null;
}
// Strip 'Safe' suffix from method name
$wrappedMethodName = substr($method, 0, strpos($method, 'Safe'));
try {
return $this->$wrappedMethodName($args);
} catch (Exception $e) {
return [];
}
}
}
class TestChild extends TestParent {
public function throwingMethod()
{
throw new RuntimeException();
}
public function succeedingMethod()
{
return 'Success';
}
}
$child = new TestChild();
// With 'Safe' try-catch invoked in parent
var_dump($child->throwingMethodSafe()); // Empty array
var_dump($child->succeedingMethodSafe()); // 'Success'
// Without 'Safe' try-catch
var_dump($child->throwingMethod()); // throws RuntimeException as expected
Output in 3v4l.org
Sidenote: Please don't catch the Exception class as it is too general and will make debugging a living hell later on ("Why is this method returning an array??")
From my personal experience create customized exception handler and return empty array if you get that exception.
These link will help you to understand the exception handling in PHP:
https://www.w3schools.com/php/php_exception.asp
http://php.net/manual/en/language.exceptions.php

How to call a method using array without object?

How can I call a method like in laravel or slim route?
Let's say I have class like this:
namespace App;
class App
{
public function getApp(){
return "App";
}
}
and I want to call on this way
$route->get('App\App','getApp');
How can I do this?
The simplest way
call_user_func_array(['App\App', 'getApp'], $params_if_needed);
php.net source call_user_func_array()
If you need to check if method exists, just use
method_exists('SomeClass','someMethod') // returns boolean
php.net method_exists()
So you Router class may be next:
class Router
{
public function get($class, $method)
{
if($_SERVER['REQUEST_METHOD'] !== 'GET') {
throw new SomeCustomNotFoundException();
}
if (!method_exists($class, $method)) {
throw new SomeCustomNoMethodFoundException();
}
call_user_func_array([$class, $method], $_REQUEST); //with params
// OR
call_user_func([$class, $method]); //without params, not sure
}
}
If you want to do in the more clever way, you can use Reflection, it will give you information about class/method existence and also give information about method params, and which of them are required or optional.
UPDATE: This example expects methods to be static. For non-static you can add check, in the Router class, for class existence (class_exists($class)) and do smth like this
$obj = new $class();
$obj->$method(); //For methods without params
UPDATE(2) to check this out go here and paste next code
<?php
class Router
{
public function get($class, $method)
{
if($_SERVER['REQUEST_METHOD'] !== 'GET') {
throw new SomeCustomNotFoundException();
}
if(!class_exists($class)) {
throw new ClassNotFoundException();
}
if (!method_exists($class, $method)) {
throw new SomeCustomNoMethodFoundException();
}
call_user_func_array([$class, $method], $_REQUEST); //with params
// OR
//call_user_func([$class, $method]); //without params, not sure
}
}
class Test
{
public static function hello()
{
die("Hello World");
}
}
$route = new Router();
$route->get('Test', 'hello');

Cahining pattern

I have a class in php that works with the chainning method, but the problem is that I want to chain the methods in some order.
class Chain {
public function foo () {
return $this;
}
public function bar () {
return $this;
}
public function some () {
return $this;
}
}
So, if I use this class, then I can chain this methods in 9 different ways (all the possible combinations of 3 elements)
But what happen if I determine that the method some always must to be chained after foo or bar and not in other way?
$chain = new Chain();
$chain->foo->bar(); //works; i.e: the method some is optional
$chain->foo()->bar()->some(); //works
$chain->bar()->foo()->some(); //works
$chain->some()->bar()->foo(); //throws an exception
I think that I can do this setting boolean values, something like: when the method foo or bar are called, then I set the value to some var to true, and when the developer calls the some function, if that var is false, then throws an exception, otherwise is allowed to continue.
But I need something more elegant, such as pattern or a built-in solution.
There is another way to do it?
The very rough example I imagine will still have some lines of code in each method
<?php
class Chain {
private $_register = array();
public function foo () {
$this->register(__METHOD__);
return $this;
}
public function bar () {
$this->register(__METHOD__);
return $this;
}
public function some () {;
$this->verify('foo'); // foo() should be called before some();
$this->register(__METHOD__);
echo 'it\'s ok';
return $this;
}
public function verify($method) {
if(array_key_exists($method, $this->_register) && $this->_register[$method] == true) {
return true;
}
else {
throw new Exception('Some exception');
}
}
public function register($method) {
$method = str_replace(__CLASS__.'::', '', $method);
$this->_register[$method] = true;
}
}
What do we do here - we have a register() and verify() methods. (they can be helpers, but for the current purpose I added them in the class.
Each method should have before it's returning value a register to itself. Calling $this->register(__METHOD__) from foo() will add in the private array 'foo' => true.
The verify() method checks if foo exist as array key and if its value is true. If it is - the script will continue. Otherwise - throws exception.
In this case:
$chain = new Chain();
$chain->bar()->some()->foo(); //throws an exception
Fatal error: Uncaught exception 'Exception' with message 'Some
exception' in ...
$chain = new Chain();
$chain->foo()->some()->foo(); // ok
it's ok
The problem here is that we establish a "convention". You need to pass __METHOD__ to the register function so after it replace the classname it will add only the method name in the array. So later, in the function where you need to verify if one or more functions are called before this, you need to use the method name as string i.e. $this->verify('foo');
Ofcourse you can play different scenarios without stripping and testing with strpos() or adding () after the methodname for easier recognition if you are verifying a method or smth else.
But at least it will save you from making for each method, different variable to fill i.e.
function foo() {
$this->_foo = true;
return $this;
}
function bar() {
$this->_bar = true;
return $this;
}
Forcing the caller to stick to a certain order of calls just as an end to itself is hardly useful at all. Supposedly what you're really interested in is to make sure the state of the object is valid when you call some() and throw an exception if it's not. In that case, yes, you would check certain indicators of your object's state and throw an exception when this state does not fulfil the requirements that some() may be called. As a concrete example:
$api = new SomeAPI;
$api->setUserID($id);
$api->setSecretKey($secret);
$api->call('something');
Here call() would check that the user id and access key has been set, otherwise it can't do its job. Whether these calls are chained or not is irrelevant and just a syntactic detail.
Alternatively, you could return certain objects of other (sub) classes from your methods which physically make it impossible to call certain methods on them if certain conditions haven't been met:
public function bar() {
if ($this->foo) {
return new SubFoo($this->foo);
} else {
return new SubBar;
}
}
This may be overly complicated though.

Call a child clases' __call method if it exists, and throw an exception if not

[edit] updated the title to more accurately reflect the problem
The problem I am trying to solve is this: I need to know if a method was called via parent:: and while I can use debug_backtrace it seems like there must be a better way to do this.
I've been looking into late static binding but perhaps I don't understand it well enough to fathom a solution.
The method in question is __call so I can't simply pass in an extra parameter as its an error to have more or less then exactly two.
The reason for trying to solve this problem is that the parent class has __call but the child may or may not have _call. If the child doesn't have it, and the parent doesn't dispatch the call, then I'd like to throw an exception or error. If the child does have the method then I'll return false (no we didn't handle this) and let the child _call method carry on.
So far my only working solution is to have the child call parent::__call wrapped in a try/catch block and have the parent throw an exception by default if it does not route the request.
ie.
class Parent {
public function __call( $method, $params ) {
if( preg_match( $this->valid, $method ) {
$this->do_stuff();
// if child has a call method, it would skip on true
return true;
}
elseif( ** CHILD HAS CALL METHOD ** ) {
// this would let the child's _call method kick in
return false;
}
else {
throw new MethodDoesNotExistException($method);
}
}
}
class Child extends Parent {
public function __call( $method, $params ) {
if( ! parent::__call( $method, $params ) ) {
do_stuff_here();
}
}
}
While throwing an exception if the parent doesn't handle the method works, I'm just trying to see if there's a more elegant solution, as using exceptions for flow-controll doesn't seem quite right. But neither does using a stacktrace to figure out the caller, either.
This should do in your parent class:
if (__CLASS__ != get_class($this))
I'm not entirely sure if this fits your needs and I also consider this kind of hacks to be really bad from the OO design point of view. However, it was a fun thing to code :)
<?php
class ParentClass
{
public function __call( $method, $params )
{
if($method === 'one')
{
echo "Parent\n";
return true;
}
elseif($this->shouldForwardToSubclass($method))
{
return false;
}
else
{
throw new Exception("No method");
}
}
protected function shouldForwardToSubclass($methodName)
{
$myClass = get_class($this);
if (__CLASS__ != $myClass)
{
$classObject = new ReflectionClass($myClass);
$methodObject = $classObject->getMethod('__call');
$declaringClassName = $methodObject->getDeclaringClass()->getName();
return $myClass == $declaringClassName;
}
else
{
return false;
}
}
}
class ChildClass1 extends ParentClass {
public function __call( $method, $params ) {
if( ! parent::__call( $method, $params ) )
{
echo "Child handle!\n";
}
}
}
class ChildClass2 extends ParentClass {
}
later doing:
$c = new ChildClass1();
$c->one();
$c->foo();
$c = new ChildClass2();
$c->foo();
would yield:
Parent
Child handle!
PHP Fatal error: Uncaught exception 'Exception' with message 'No method' in /home/andres/workspace/Playground/test.php:18
Stack trace:
#0 /home/andres/workspace/Playground/test.php(58): ParentClass->__call('foo', Array)
#1 /home/andres/workspace/Playground/test.php(58): ChildClass2->foo()
#2 {main}
HTH

Categories