I'm using an API that returns different values, and I want to dynamically tell my class to run a function with the same name (so i won't need a huge switch or if/else mess).
How can I declare the object method song.pause?
How can I use the object method song.pause?
This might be an XY Problem, so is there a better way to do this? One alternative i thought of is to always call str_replace('.','_',$type) and set functions without the periods. Thoughts?
Example:
<?php
class MyClass {
...
...
$type = "song.pause"; // This value is returned from another API I can't control
if (property_exists('MyClass', $type)) {
$success = $this->{$type}(); // ???
} else {
header("HTTP/1.1 400 Invalid type: $type);
exit;
}
public function 'song.pause'() { // obviously incorrect :)
???
}
Given a return of song.pause, conceptually song should be the class name and pause should be the method, consider this possibility:
class MyClass {
protected $classes = array();
function processResponse($response) {
// $response example is "song.pause"
list($class, $method) = explode('.', $response);
if(!class_exists($class)) {
// Class doesn't exist
die("Class name {$class} doesn't exist! Exiting...");
}
// Instantiate class
$this->classes[$class] = new $class();
if(!method_exists($this->classes[$class], $method)) {
// Method doesn't exist within specified class
die("Method name {$method} doesn't exist within {$class}. Exiting...");
}
// Call method
$result = $this->classes[$class]->{$method}();
return $result;
}
}
Your logic implementation would be something like this:
class song {
public function pause() {
return 'foobar';
}
}
Here's an example.
Unfortunately, what you're generally asking is not supported. From the manual:
Function names follow the same rules as other labels in PHP. A valid
function name starts with a letter or underscore, followed by any
number of letters, numbers, or underscores. As a regular expression,
it would be expressed thus: [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*.
This also applies to class methods.
As the walkaround, you may follow the way that you suggested yourself:
$type = 'song.pause';
$type = str_replace('.', '_', $type);
$this->{$type}(); // will call song_pause()
OR use "dark" magic:
<?php
// header('Content-Type: text/plain; charset=utf-8');
class Test {
function __call($method, $args){
// do redirect to proper processing method here
print_r($method);
echo PHP_EOL;
print_r($args);
}
}
$x = new Test();
$x->{'song.pause'}(1,2,3);
?>
Shows:
song.pause // < the method
Array // < arguments
(
[0] => 1
[1] => 2
[2] => 3
)
However, the "long" and really transparent way, which I completely agree with, is suggested by #scrowler.
#HAL9000 is right: what you want is not supported. One potential workaround is:
Define the handlers:
$typeHandlers = array();
$typeHandlers['song.pause'] = function () {
echo 'Pause!'; // or whatever...
};
Call the appropriate handler:
$typeHandlers[$type]();
Related
How can I define a PHP function with a parameter accepting a fixed set of values similar to filter_var which accepts as argument various values as in caps:
http://php.net/manual/en/filter.filters.validate.php
I am guessing it will be something like:
function f(int $params) { ... }
but the question is how to make my own fixed caps typed associations?
Example to clarify:
Currently I have 2 functions:
function defineDirConstant($constant, $dirname) {
if (is_dir($dirname) && is_readable($dirname)) {
define(__NAMESPACE__ . '\\' . $constant, $dirname . DIRECTORY_SEPARATOR);
} else {
// some other logic
}
}
function defineFileConstant($constant, $filename) {
if (is_file($filename) && is_readable($filename)) {
define(__NAMESPACE__ . '\\' . $constant, $filename);
} else {
// some other logic
}
}
which I am willing to combine into one:
function defineFilesystemConstant($constant, $filename, int $type) {
// some logic considering the $type
}
I know in this particular case I can use a Boolean for type but my question is in general: how to make a humanly readable parameter which is able to be typed as text. E.g. the function call would be
f("ONE", "foo.txt", TYPE_FILE);
f("BAR", "bar", TYPE_DIR);
f("JIM", "jimcarrey", TYPE_SOMETHING_ELSE);
I hope that clarifies.
I think what you are looking for is the define() function - you can use that to create constants like those used by the filter_var function.
In your example, you would create these constants:
define('TYPE_FILE', 1);
define('TYPE_DIR', 2);
define('TYPE_SOMETHING_ELSE', 3);
which you can then use in, for example, f("BAR", "bar", TYPE_DIR);.
Or if you want to avoid global constants, you can scope them using a class:
class FileSystemType {
const FILE = 1;
const DIR = 2;
const SOMETHING_ELSE = 3;
}
Which you can then access using:
f("BAR", "bar", FileSytemType::DIR);
Look here http://php.net/manual/en/migration56.new-features.php:
function f($req, $opt = null, int ...$params) {
printf('$req: %s; $opt: %s; number of params: %s'."\n",
$req, $opt, count($params));
}
This function take $reg and $opt as parameters and the array $params hold all next parameters and convert it to int
So f(1,2,3,4) has $reg=1 $opt=2, $params=[3,4].
Or f(1,2,3,4,'5') has $reg=1 $opt=2, $params=[3,4,5].
Know you can check $params and only use valid parameters [ENUMS].
To define ENUMS you can do:
interface MyEnums {
const VALUE1 = 1;
const VALUE2 = 2;
}
echo MyEnums::VALUE1;
So if you combine these 2 technics you have something simlliar to your example.
f(1,2,MyEnums::VALUE1,MyEnums::VALUE2);
Keep the comment from #this.lau_ above in mind.
I am working on a custom DB Table Mapper in PHP.
Is it possible in PHP to make something like "virtual methods" to access the properties? Like Methods, that don't really exist.
For Example: A class "user" has the property "$name", i don't want to create a "Get" Method for this one, but i want to access the property via a virtual Method, like this: $user->GetName();
I was thinking of working with Conventions. So everytime a "virtual" Method has been called, you catch it, and check if it has the prefix "Get" or "Set".
If it has the prefix "Get" you strip the part after "Get" and make it lowercase, so you have the property you want to access.
My Idea (Pseudo Code):
public function VirtualMethodCalled($method_name)
{
//Get the First 3 Chars to check if Get or Set
$check = substr($method_name, 0, 3);
//Get Everything after the first 3 chars to get the propertyname
$property_name = substr($method_name, 3, 0);
if($check=="Get")
{
return $this->{$property_name};
}
else if($check=="Set")
{
$this->{$property_name};
$this->Update();
}
else
{
//throw exc
}
}
You can use a magic method to achieve this, example:
class A {
private $member;
public function __call($name, $arguments) {
//Get the First 3 Chars to check if Get or Set
$check = substr($method_name, 0, 3);
//Get Everything after the first 3 chars to get the propertyname
$property_name = substr($method_name, 3);
if($check=="Get")
{
return $this->{$property_name};
}
else if($check=="Set")
{
$this->{$property_name} = $arguments[0]; //I'm assuming
}
else
{
//throw method not found exception
}
}
}
I'm mainly using the code you provided for the contents. You can obviously extend this to also handle things like function name aliases or whatever you need.
I'm aware that you can have PHP functions with optional arguments like so:
function do_something($argument = null)
{
// argument not provided
if ($argument === null)
........
}
Consider the case that null/false and other values are all valid arguments to my function. How can I determine whether an argument was provided or not?
do_something(null); // argument provided
do_something(false); // argument provided
do_something(0); // argument provided
do_something("test"); // argument provided
do_something(new stdClass()); // argument provided
do_something(); // nothing provided
How can I detect the last case? I have thought about using func_num_args which would work in most cases but it doesn't work if I have several optional arguments.
Is there anything that solves this problem?
func_num_args() should work exactly as you want it to, because you might be assuming something that's actually not the case: You can't have optional arguments left out if they are in the middle of your arguments list.
So let's look at this function:
function test1 ($param1 = null, $param2 = null) {
return func_num_args();
}
If I call that with different parameter combinations I get the following results:
test1() => 0
test1(true) => 1
test1(true, true) => 2
There is just no way to call the function in a way where $param2 would be set while $param1 isn't. So you can map every possible output of func_num_args() to exactly one parameter configuration.
In the example above you can rely on the fact that
if the return value is 1, $param2 definitely hasn't been set, while $param1 has been.
For 0 it's 100% sure that neither one has been given.
And, of course, if it's 2 both are there.
What you actually would need are named parameters, as many other languages have them. PHP doesn't at the moment. NikiC actually wrote an RFC that suggests the addition of named parameters to PHP, but I think that's still way off in the future. You can check that out here: https://wiki.php.net/rfc/named_params
As these are not yet available, here are a few workarounds you can try:
Workaround 1
If you really need to be able to have all the parameters optional, try a parameter array:
function test1 (array $opts) {
if (!isset($opts['opt1'])) { $opts['opt1'] = 'default1'; }
if (!isset($opts['opt2'])) { $opts['opt2'] = 'default2'; }
}
Then you can call it like this:
test1(array('opt2' => true))
It would set the first parameter to "default1" while keeping the second. And there are definitely better and more elegant ways to do this (e.g. using an object instead), but the general idea is the same.
Workaround 2
You could also go with alias functions:
function test ($param1, $patam2) { ... ]
function testNoParam1 ($param2) {
test("default1", $param2);
}
That at least makes it very easy to read, but of course you need to pick the right function depending on the parameters you have.
Workaround 3
By adding a lot of additional code you could get really fancy and use a FactoryObject:
class FunctionExecutor {
private $param1 = "default1";
private $param2 = "default2";
public function param1($val) {
$this->param1 = $val;
return $this;
}
public function param2($val) {
$this->param2 = $val;
return $this;
}
public function execute() {
return yourFunction($this->param1, $this->param2);
}
}
This could be used like this:
$doSomething = new FunctionExecutor();
$returnValue = $doSomething->param2(42)->execute();
In this approach it would probably be a better idea to actually put your function into the object instead of defining it globally. Anyway...this is definitely a possibility, but not the most practical one. Just wanted to add it, because it has some benefits.
perhaps this will help: http://www.php.net//manual/en/function.func-get-args.php
$args = func_get_args();
if(!isset($arg[0])) {
echo 'no argument';
}
or
isset(func_get_arg(0));
Passing "null", "0", or "false" means that you allocate memory to store a variable, regardless it's scope, type, or size. Then it is used as a parameter to a function.
In PHP you cannot override functions by arguments, but you can access them by calling the "func_get_args()", and this is the only way to handle different numbers / types of arguments passed to a function:
function do_something() {
$args = func_get_args();
//do_something(stdClass, 1)
if($args[0] instanceof stdClass && is_numeric($args[1])) {
//handle
//return
//do_something(1, "string")
} else if(is_numeric($args[0]) && is_string($args[1])) {
//handle
//return
}
throw new Exception('invalid arguments');
}
do_something(new StdClass(), 100); //ok
do_something(100, "hell world") // ok
do_someting(); //throws Exception('invalid arguments');
In PHP 7, you can do:
function action(...$args) {
if (count($args) === 0) {
return action_default();
}
$var1 = array_shift($args);
$var2 = array_shift($args);
$var3 = array_shift($args);
// etc.
}
I have a method in PHP which calls a SOAP service, parses some data and returns it.
It will return the same data - it asks how many records in a data object.
I need to call it twice with a pass.
My question is, what is best practice for structuring this in PHP? I've tried to see if the function has been called already.Do I use static variables / functions?
function MinimumRequired() {
return $this->NumberPeopleJoined();
}
function NumberPeopleJoined () {
if (isset($NumberPeople)) {
Debug::Show($NumberPeople);
}
static $NumberPeople;
$NumberPeople = Surge_Controller::NumberPeopleJoined();
return $NumberPeople;
}
Thanks!
Just create a local class member, and check if that has a value. If not, set the value to whatever is retrieved from Surge_Controller, and if it was already set, just return the value:
<?php
class Surge_Controller {
static public function NumberPeopleJoined() {
echo "Surge_Controller::NumberPeopleJoined() got called.\n";
return 2;
}
}
class Foo {
protected $cacheNumberPeople;
function MinimumRequired() {
return $this->NumberPeopleJoined();
}
function NumberPeopleJoined () {
if( !isset( $this->cacheNumberPeople ) ) {
$this->cacheNumberPeople = Surge_Controller::NumberPeopleJoined();
}
return $this->cacheNumberPeople;
}
}
$foo = new Foo( );
echo $foo->numberPeopleJoined( ) . "\n";
echo $foo->numberPeopleJoined( ) . "\n";
Output:
$ php foo.php
Surge_Controller::NumberPeopleJoined() got called.
2
2
The simple way is to have a global variable, and check if is "true", and set it to true at the end of your function. The value can be cached too...
But if you want to make your code fun, you can use underscore:
http://brianhaveri.github.com/Underscore.php/#once
http://brianhaveri.github.com/Underscore.php/#memoize
So, I have a object with structure similar to below, all of which are returned to me as stdClass objects
$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;
etc... (note these examples are not linked together).
Is it possible to use variable variables to call contact->phone as a direct property of $person?
For example:
$property = 'contact->phone';
echo $person->$property;
This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this.
Any ideas?
In response to answers relating to proxy methods:
And I would except this object is from a library and am using it to populate a new object with an array map as follows:
array(
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
);
and then foreaching through the map to populate the new object. I guess I could envole the mapper instead...
If i was you I would create a simple method ->property(); that returns $this->contact->phone
Is it possible to use variable variables to call contact->phone as a direct property of $person?
It's not possible to use expressions as variable variable names.
But you can always cheat:
class xyz {
function __get($name) {
if (strpos($name, "->")) {
foreach (explode("->", $name) as $name) {
$var = isset($var) ? $var->$name : $this->$name;
}
return $var;
}
else return $this->$name;
}
}
try this code
$property = $contact->phone;
echo $person->$property;
I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first.
For example:
$property = 'contact->phone';
echo $person->{$property};
The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly.
$xml->{a-disallowed-field}
If it is legal it does not mean it is also moral. And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. Take a look at the law of demeter:
Law of Demeter
try this if you really really want to:
json_decode(json_encode($person),true);
you will be able to parse it as an array not an object but it does your job for the getting not for the setting.
EDIT:
class Adapter {
public static function adapt($data,$type) {
$vars = get_class_vars($type);
if(class_exists($type)) {
$adaptedData = new $type();
} else {
print_R($data);
throw new Exception("Class ".$type." does not exist for data ".$data);
}
$vars = array_keys($vars);
foreach($vars as $v) {
if($v) {
if(is_object($data->$v)) {
// I store the $type inside the object
$adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
} else {
$adaptedData->$v = $data->$v;
}
}
}
return $adaptedData;
}
}
OOP is much about shielding the object's internals from the outside world. What you try to do here is provide a way to publicize the innards of the phone through the person interface. That's not nice.
If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures:
class conv {
static function phone( $person ) {
return $person->contact->phone;
}
}
// imagine getting a Person from db
$person = getpersonfromDB();
print conv::phone( $p );
If ever you need a more specialized function, you add it to the utilities. This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability.
Another way is to 'extend' the Person class with conveniences, built around the core class' innards:
class ConvPerson extends Person {
function __construct( $person ) {
Person::__construct( $person->contact, $person->name, ... );
}
function phone() { return $this->contact->phone; }
}
// imagine getting a Person from db
$person = getpersonfromDB();
$p=new ConvPerson( $person );
print $p->phone();
You could use type casting to change the object to an array.
$person = (array) $person;
echo $person['contact']['phone'];
In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures.
In the example above, person has contact and dob. The contact also contains address. Trying to access the data from the uppermost level is not uncommon when writing complex database applications. However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects.
As much as I hate saying it, you could do an eval :
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. Do remember that magic methods are somewhat slow though.
class Person {
private $fields = array();
//...
public function __get($name) {
if (empty($this->fields)) {
$this->fields = get_class_vars(__CLASS__);
}
//Cycle through properties and see if one of them contains requested field:
foreach ($this->fields as $propName => $default) {
if (is_object($this->$propName) && isset($this->$propName->$name)) {
return $this->$propName->$name;
}
}
return NULL;
//Or any other error handling
}
}
I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. Thank for you for your answers.
Edit:
If you are interested:
public function __construct($data)
{
$this->_raw = $data;
}
public function getContactPhone()
{
return $this->contact->phone;
}
public function __get($name)
{
if (isset($this->$name)) {
return $this->$name;
}
if (isset($this->_raw->$name)) {
return $this->_raw->$name;
}
return null;
}
In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. You can then 'decorate' your objects with the same retrieval code.
An example of 'retrieval only' decoration code:
function retrieve( $obj, $path ) {
$element=$obj;
foreach( $path as $step ) {
$element=$element[$step];
}
return $element;
}
function decorate( $decos, &$object ) {
foreach( $decos as $name=>$path ) {
$object[$name]=retrieve($object,$path);
}
}
$o=array(
"id"=>array("name"=>"Ben","surname"=>"Taylor"),
"contact"=>array( "phone"=>"0101010" )
);
$decorations=array(
"phone"=>array("contact","phone"),
"name"=>array("id","name")
);
// this is where the action is
decorate( $decorations, &$o);
print $o->name;
print $o->phone;
(find it on codepad)
If you know the two function's names, could you do this? (not tested)
$a = [
'contactPhone' => 'contact->phone',
'contactEmail' => 'contact->email'
];
foreach ($a as $name => $chain) {
$std = new stdClass();
list($f1, $f2) = explode('->', $chain);
echo $std->{$f1}()->{$f2}(); // This works
}
If it's not always two functions, you could hack it more to make it work. Point is, you can call chained functions using variable variables, as long as you use the bracket format.
Simplest and cleanest way I know of.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Usage
echo getValueByPath($person,'contact->email');
// Returns the value of that object path