Check if something is a function before calling it - php

I have a class that looks a bit like this:
<?php
namespace App\Http\Controllers;
use Exception;
use Illuminate\Http\Request;
class FormatAddressController extends Controller
{
public function __construct()
{
$this->middleware(['api-auth']);
$this->middleware(['api-after']);
}
public function format(Request $request) {
// TODO first, get customer settings to see what functions to run, and how to run them
// but, assume that the settings come as an array where the key is the function name
// and the value is one of NULL, false, or settings to pass through to the function
$settings = ['isoAndCountry' => true, 'testFunc' => ['testSetting' => 'test setting value']];
$address = $request->input('address');
$errors = [];
foreach ($settings as $funcName => $funcSettings) {
try {
$address = $this->$funcName($funcSettings, $address); // every function has to return the modified address
} catch(Exception $e) {
$errors[$funcName] = $e;
}
}
return response()->json([
'address' => $address,
'errors' => $errors
]);
}
public function isoAndCountry($settings, $address) {
// TODO
return $address;
}
}
Now, when I call this function, isoAndCountry, through that settings loop I defined above, it works! It works just fine.
However I tried following this thread and checking is_callable and... it errors:
if (is_callable($this->$funcName)) {
try {
$address = $this->$funcName($funcSettings, $address); // every function has to return the modified address
} catch(Exception $e) {
$errors[$funcName] = $e;
}
}
How can I check if it's callable? Why doesn't this work?

You have to use method_exists here to check if the method really exists in the class.
foreach ($settings as $funcName => $funcSettings) {
if (method_exists($this, $funcName)) {
$this->$funcName($funcSettings, $address);
}
}
The reason why is_callable will not work in your scenario is because Laravel controllers has a __call magic method which will handle undefined methods, so running is_callable on any non existing methods would return true.
Take the following class as an example:
class A
{
public function __construct()
{
var_dump(is_callable([$this, 'testFunc']));
}
}
The output of new A would be false. However, if you add the following into the class:
public function __call($name, $arguments)
{
//
}
Now the output of the var_dump would return true.
You can read more about the __call scenario I've mentioned right here: https://www.php.net/manual/en/function.is-callable.php#118623
For more information about __call: https://www.php.net/manual/en/language.oop5.overloading.php#object.call

May be this can also solve the problem :
if(method_exists($this,$funcName)){ ... }

You can use
if (is_callable([$this, $funcName])) { ...
instead.
The way you have it written with is_callable($this->$funcName), it's going to look for a property called $funcName on $this, (which probably doesn't exist) and check if that's callable. If you use that array syntax it will evaluate the named method instead.
It may be simpler in this case to use
if (method_exists($this, $funcName)) {
since you're using it in another method of the same object, if the method exists it should be callable.

You need to differentiate between class properties and methods. For example in this class:
class A {
private $foo = null;
public function getFoo() {
return $this->foo;
}
}
the private $foo is property and can be checked by property_exists()
the public function getFoo() is method and can be checked by method_exists()
https://www.php.net/manual/en/function.property-exists.php
https://www.php.net/manual/en/function.method-exists.php
I think that $this->$funcName works only for properties.

Related

Return variable class name from php factory

I've got a factory that I want to return a ::class from. However, the factory could potentially return a couple dozen different types (determined by the type of object passed into the factory), named TypeOneObject, TypeTwoObject etc. Is it possible to return the class using a variable, something like this?
$type = $myrequiredclass->getType();
return $type."Object"::class; // wanting TypeOneObject::class
It seems like no matter how I construct this return statement I always get PHP Parse error: syntax error, unexpected '::'
I know it'd be easy enough to do with a big if/then or switch but I'd like to avoid that.
Here's a more fleshed out scenario:
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$type = $class->getType()."Object";
return $type::class;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
$object::whoAreYou();
The best way to get the class name from the $type instance is to use php get_class_methods function. This will get us all the methods inside the class instance. from there we can filter and use call_user_func to call the method and get the right values.
class TypeOneObject
{
public static function whoAreYou()
{
return 'Type One Object!';
}
}
class MyRequiredClass
{
public function getType()
{
return 'TypeOne';
}
}
class MyFactory
{
public static function getFactoryObject(MyRequiredClass $class)
{
$methods = get_class_methods($class);
$method = array_filter($methods, function($method) {
return $method == 'getType';
});
$class = new $class();
$method = $method[0];
$methodName = call_user_func([$class, $method]);
$objectName = sprintf('%sObject', $methodName);
return new $objectName;
}
}
$object = MyFactory::getFactoryObject(new MyRequiredClass());
echo $object::whoAreYou();
Output
Type One Object!

How can I achieve something like $app->error()->encode() in PHP?

I want to include this function in a class. This function will essentially json_encode the output of the previous function (I hope that makes sense).
I want to do something like this:
<?php
$app = new App;
// $app->error(); // Should return ['some error', 'some other error']
echo $app->error()->encode(); // Should return {'errors': ['some error', 'some other error']}.
Also what's the correct term for such function? I've been searching but couldn't find anything as I didn't really know what I was looking for.
Thanks!
Edit
I think you got that wrong. It's my mistake I didn't mention it before.
That's just an example. Just like in frameworks like Slim, where you can do something like:
$response->getBody()->write('Something');
I want to do something similar to that. Not just that. I want to learn how it's done.
Here is some boilerplate code you could use. The idea is that you should make the error method return an object of yet another class. That object should then in turn have the encode method.
In your example, you want $app->error() to return an array. For it to behave as an array, we can extend the ArrayObject class.
Secondly, you want that same $app->error() to expose another method encode. So you define that method in that same class mentioned above:
// Extend ArrayObject to make objects behave as arrays
class ErrorMsg extends ArrayObject {
// Add method to return JSON string
public function encode() {
return json_encode(array("errors" => $this->getArrayCopy()));
}
}
class App {
private $error;
public function doSomething() {
// For demo sake, just set an error:
$this->error = ["An error occurred in doSomething", "No further info"];
}
public function error() {
// This is the key: instantiate and return another object
return new ErrorMsg($this->error);
}
}
$app = new App;
// Set an error condition in $app
$app->doSomething();
// Loop over error array
foreach ($app->error() as $index => $error) {
// Display the error
echo "Error $index is: $error\n";
}
// Display the JSON encoding of the same.
echo $app->error()->encode() . "\n";
Output:
Error 0 is: An error occurred in doSomething
Error 1 is: No further info
{"errors":["An error occurred in doSomething","No further info"]}
See it run on eval.in
General idea to chain method calls
In general, when you want your objects to support chained -> notation, you must make sure to define each method as returning yet another object with its own methods. Then those methods can again return objects, with again exposed methods, etc. And so you can chain-call on and on.
So if you want to be able to write:
$a = new A();
$result = $a->b()->c()->d();
...then your code would look something like this:
class D {
// ...
}
class C {
private $d;
public function C() { // constructor
$this->d = new D();
}
public function d() {
return $this->d;
}
}
class B {
private $c;
public function B() { // constructor
$this->c = new C();
}
public function c() {
return $this->c;
}
}
class A {
private $b;
public function A() { // constructor
$this->b = new B();
}
public function b() {
return $this->b;
}
}
Of course, this is just the structure, and you'd pass some specific data to either the constructors and/or methods. But the main idea is that you never return simple types (String, Number, ...), but always objects.

Mockery object argument validation issue

Consider the example classes (apologies for it being so convoluted, but it's as slim as possible):
class RecordLookup
{
private $records = [
13 => 'foo',
42 => 'bar',
];
function __construct($id)
{
$this->record = $this->records[$id];
}
public function getRecord()
{
return $this->record;
}
}
class RecordPage
{
public function run(RecordLookup $id)
{
return "Record is " . $id->getRecord();
}
}
class App
{
function __construct(RecordPage $page, $id)
{
$this->page = $page;
$this->record_lookup = new RecordLookup($id);
}
public function runPage()
{
return $this->page->run($this->record_lookup);
}
}
In which I want to test App whilst mocking RecordPage:
class AppTest extends \PHPUnit_Framework_TestCase
{
function testAppRunPage()
{
$mock_page = \Mockery::mock('RecordPage');
$mock_page
->shouldReceive('run')
->with(new RecordLookup(42))
->andReturn('bar');
$app = new App($mock_page, 42);
$this->assertEquals('Record is bar', $app->runPage());
}
}
Note: the expected object argument ->with(new RecordLookup(42)).
I would expect this to pass however Mockery returns throws No matching handler found for Mockery_0_RecordPage::run(object(RecordLookup)). Either the method was unexpected or its arguments matched no expected argument list for this method.
I'm assuming this is because a strict comparison is used for the arguments expected through with() and new RecordLookup(42) === new RecordLookup(42) evaluates as false. Note new RecordLookup(42) == new RecordLookup(42) evaluates as true so if there was someway of relaxing the comparison it would fix my problem.
Is there a proper way to handle expected instance arguments in Mockery? Maybe I'm using it incorrectly?
You can tell mockery that a RecordLookup instance (any) should be received:
$mock_page
->shouldReceive('run')
->with(\Mockery::type('RecordLookup'))
->andReturn('bar');
But this will match any instance of RecordLookup. If you need to dig inside the object and check if it's value is 42, then you can employ a custom validator:
$mock_page
->shouldReceive('run')
->with(\Mockery::on(function($argument) {
return
$argument instanceof RecordLookup &&
'bar' === $argument->getRecord()
;
}))
->andReturn('bar');
There are more options, well explained in the docs.
As an alternative, the documentation proposes to use equalTo($object).
For example:
$userRepository->shouldReceive('create')
->once()
->with(\Hamcrest\Core\IsEqual::equalTo(
new User(0, "Test", "Dummy", "fakelogin")));

can I pass __construct parameters after a class has been instantiated into an object?

I have a similar code snippet like this
class Search
{
public function search($for, $regEx, $flag) //I would like this to be the constructor
{
// logic here
return $this;
}
}
Then I have another class that creates an object from it, later than tries to use the object.
class MyClass
{
public function start()
{
$this->search = new Search();
}
public function load()
{
$this->search($for, $regEx, $flag);
}
}
My question is, is it possible to create an object first THEN give it the parameters?
I know there are some way around this BUT I only ask because I want to use the object like this
$this->search($params);
// I have my methods chained, so I could use it in one line like
// $this->search($params)->hasResults();
if ($this->search->hasResults()) {
echo 'found stuff';
} else {
echo 'didn't find anything';
}
The way I have it set up right now, I would need to use it like this
$this->search->search($params);
if ($this->search->hasResults()) {
echo 'found stuff';
} else {
echo 'didn't find anything';
}
I have a method called search() that does the logic, and I don't want to be redundant in my naming nor do I want to change the name of the method.
I know another way to keep the visual appeal sane I could pass a variable like so
$search = $this->search->search($params);
then
$search->hasResults();
At the same time I am trying to introduce myself to new OOP concepts and learn from them. Would this require passing things by reference? or setting up some type of magic method?
While the previous anwsers show that you can, I wouldn't use it, because it breaks the concept of encapsulation. A proper way to achieve what you want is the following
class Search
{
public function __constructor($for='', $regEx='', $flag='')
{
$this->Setup($for, $regEx, $flag);
}
public function Setup($for, $regEx, $flag)
{
//assign params
//clear last result search
//chain
return $this;
}
public function search()
{
// logic here
return $this;
}
}
In this way, you can reuse the object and have the params in the constructor, without breaking encapsulation.
Yes it is possible
See the below example
<?php
class a{
public $a = 5;
public function __construct($var){
$this->a = $var;
}
}
$delta = new a(10);
echo $delta->a."\n";
$delta->__construct(15);
echo $delta->a."\n";
Output will be:
10 15
Yep, you can.
class Example {
public $any;
function __counstruct($parameters,$some_text) {
$this->any=$some_text;
return $this->any;
}
}
You can call constructor:
$obj = new Example (true,'hello');
echo $obj->any;
$obj->__construct(true,'bye-bye');
echo $obj->any;
I was able to create the visual coding I wanted by using the __call() magic method like this
public function __call($name, $params)
{
$call = ucfirst($name);
$this->$name = new $call($params);
}
from there I could use this
$this->test->search($params);
$this->test->search->hasResults();
I of course now set the search() method to the class constructor

Magic Method __set() on a Instantiated Object

Ok i have a problem, sorry if i cant explaint it clear but the code speaks for its self.
i have a class which generates objects from a given class name;
Say we say the class is Modules:
public function name($name)
{
$this->includeModule($name);
try
{
$module = new ReflectionClass($name);
$instance = $module->isInstantiable() ? $module->newInstance() : "Err";
$this->addDelegate($instance);
}
catch(Exception $e)
{
Modules::Name("Logger")->log($e->getMessage());
}
return $this;
}
The AddDelegate Method:
protected function addDelegate($delegate)
{
$this->aDelegates[] = $delegate;
}
The __call Method
public function __call($methodName, $parameters)
{
$delegated = false;
foreach ($this->aDelegates as $delegate)
{
if(class_exists(get_class($delegate)))
{
if(method_exists($delegate,$methodName))
{
$method = new ReflectionMethod(get_class($delegate), $methodName);
$function = array($delegate, $methodName);
return call_user_func_array($function, $parameters);
}
}
}
The __get Method
public function __get($property)
{
foreach($this->aDelegates as $delegate)
{
if ($delegate->$property !== false)
{
return $delegate->$property;
}
}
}
All this works fine expect the function __set
public function __set($property,$value)
{
//print_r($this->aDelegates);
foreach($this->aDelegates as $k=>$delegate)
{
//print_r($k);
//print_r($delegate);
if (property_exists($delegate, $property))
{
$delegate->$property = $value;
}
}
//$this->addDelegate($delegate);
print_r($this->aDelegates);
}
class tester
{
public function __set($name,$value)
{
self::$module->name(self::$name)->__set($name,$value);
}
}
Module::test("logger")->log("test"); // this logs, it works
echo Module::test("logger")->path; //prints /home/bla/test/ this is also correct
But i cant set any value to class log like this
Module::tester("logger")->path ="/home/bla/test/log/";
The path property of class logger is public so its not a problem of protected or private property access.
How can i solve this issue? I hope i could explain my problem clear.
EDIT:
A simple demonstration
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints 333
What i need is
Modules::Name("XML_Helper")->xmlVersion ="Hello"; // default is 333
$a = Modules::Name("XML_Helper")->xmlVersion; // now $a should contain "Hello"
echo $a; // prints Hello
I realise you already said that path is public, but it's still worth mentioning: If you're using PHP 5.3.0+, note this quirk of property_exists():
5.3.0 | This function checks the existence of a property independent of
accessibility
In other words, if you check if (property_exists($delegate, $property)), you have no guarantee you have access to $delegate->$property for writing (or reading, for that matter, but you are trying to write).
As for actual troubleshooting: You could try checking if your if (property_exists($delegate, $property)) statement actually executes. If it doesn't, check the case of $property.
Sidenote: It's fairly hard to read the code you posted up, which makes it a bit of a pain to troubleshoot. Could you edit your post and indent it properly?
The path property of class logger is public so its not a problem of
protected or private property access.
That's your problem. From the docs:
__set() is run when writing data to inaccessible properties.
That suggests that __set() is not called for public properties.

Categories