Class Base {
function processAction($methodName, $params) {
call_user_func_array(array($this,$methodName), $params);
}
}
class Child1 extends Base {
function getData($param1, $param2, $timestamp) {}
}
class Child2 extends Base {
function getInfo($param1) {}
}
I will be calling in the processAction to call into the methods of the child classes.
I have multiple child classes with different method signatures. I want to pass in $timestamp to some of them. The issue is, I do not want to make timestamp an optional parameter for methods that do not require. I want to be able to pass in $timestamp to only relevant methods. How will i do that ?
EDIT
I plan to add $timestamp just before cal_user_func_array() call.
$array_push($params, $timestamp);
But that would mean, that every method would expect $timestamp, which i don't want.
As mentioned in my comments, php only cares that you pass enough arguments to match the number of parameters declared. If you set a default value for a parameter, then it doesn't even care about that.
The function will "ignore" any extra parameters you pass to it, in the sense that php doesn't care and won't throw an error. However, note that any extra arguments passed to your function are exposed to the function. For example, you can use func_get_args() to get an array of values passed to the function, regardless of how many parameters were actually declared.
Example 1:
function myFunc() {}
myFunc(); // good
myfunc('foo'); // good
Example 2:
function myFunc($p1) {}
myFunc(); // bad
myfunc('foo'); // good
myfunc('foo','bar'); // good
Example 3:
function myFunc($p1='') {}
myFunc(); // good
myfunc('foo'); // good
Example 4:
function myFunc() {
print_r(func_get_args());
}
myFunc('foo','bar');
/*output:
Array
(
[0] => foo
[1] => bar
)
*/
Now let's say for shits and grins you do in fact want to enforce only passing relevant args to the methods. There are several ways to enforce this, depending on what you ultimately want to do. If you want to make it to where named arguments are passed, you're going to have to restructure your code to accept an associative array of params. Then you can write some conditions to figure out which ones were passed and pass them along or throw an exception or whatever. If you only care about number of arguments, it will be easier, because you can more or less leave your code structure as-is, and count the number of params passed.
Now, as far as figuring out how many parameters a given method has. Here is an example using the Reflection class to point you in the right direction:
Class Base {
function processAction($methodName, $params) {
// setup a reflection method object
$refMeth = new ReflectionMethod(get_class($this).'::'.$methodName);
// get a list of the parameter names, if you want to go the 'named' route
foreach($refMeth->getParameters() as $param) {
$sigParams[] = $param->getName();
}
// echo out as example
print_r($sigParams);
// or just get the number of params for the method
echo $refMeth->getNumberOfParameters();
call_user_func_array(array($this,$methodName), $params);
}
}
class Child1 extends Base {
function getData($param1, $param2, $timestamp) {}
}
class Child2 extends Base {
function getInfo($param1) {}
}
// example:
$x = new Child1();
$x->processAction('getData',array('foo','bar','12345'));
/*output
Array
(
[0] => param1
[1] => param2
[2] => timestamp
)
3
*/
So there's an example of how to find out the expected params for a given method, so you can compare to passed args and explicitly call call_user_func_array with only relevant values so that the extras (even though php would 'ignore' them) won't even be exposed, or throw an exception instead, etc..
Related
I have a function like this:
public function myfunc ($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8) {
// do something
}
See? My function gets 8 arguments. Yes it works as well, but you know, it's ugly kinda ..! Isn't there any better idea? For example passing just one array contains all argument. Is it possible? or even something similar.
I do it this way..
$params = [];
put things in params..
$params[] = $a;
$params[] = $b;
pass the array to function
myFunction($params);
The function accept array as arg like, definition:
public function myFunction($params = []){}
Pass something in, and var_dump to check for yourself...
OK, now I know that it's an SQL operation you're doing then the best approach would be an associative array (assuming PDO and prepared statements).
public function myFunc (array $data)
{
// Using 3 values for example!
$stmt = $this -> pdo -> prepare ("INSERT INTO TABLE thisTable (
col1,
col2,
col3
) VALUES (
:val1,
:val2,
:val3
)");
if (false !== $stmt execute ($data))
{
return $stmt -> rowCount ();
} else {
return 0;
}
}
You'd call it with an array with the correct parameters in it:
$success = (bool) $obj -> myFunc (["val1" => "First value to insert",
"val2" => "Second value to insert",
"val3" => "Third value to insert"]);
It depends whether myfunc belongs to an exposed api or not (i.e.: public)
If it is public, the signature must break when you update the underlying model (your insert query), otherwise errors will be committed on the client-side.
With an array, you're losely mapping your Model to your Application, and you'd just expect programmers to send you the right values. With a tight/restricting mapping, this kind of error cannot happen.
I think that if you're saving an item in the database, you actually need all these fields. It is unelegant indeed, but it's not an anti-pattern as none of them are optional. And even if one or two were, it would not be a concern.
What you could do to improve your api is either:
If there are situation where you need to pass only a few of the parameters (i.e. most are optional or depend on a particular scenario) then you could specialize the method into separate functions. But PHP not accepting function polymorphism is quite a pain in the neck for this kind of things; you'd have to name the methods differently.
public function myfunctosavedatainaparticularcase ($arg1, $arg2, $arg3, $arg4)
// do something
}
public function myfunctosavedatainanotherparticularcase ($arg5, $arg6, $arg7, $arg8)
// do something
}
Use an object model mapper. For example, suppose you're saving a User data. You'd just pass a User object to the method:
public function myfunc (User $user)
// map fields to the User signature.
}
This would be acceptable if you're in control of the User class since you'd have to change it to reflect model changes.
Use an ORM to handle this for you. You'd only have to update the xml specifications of the schema after you decide to change the database model, all necessary changes would be propagated to the application automatically. Of course the objects definition would change but that is inevitable.
In perl I'm used to doing
my $foo = new WhatEver( bar => 'baz' );
and now I'm trying to figure out if PHP objects can ever be constructed this way. I only see this:
my $foo = new WhatEver();
$foo->{bar} = 'baz';
is it possible to do it in one step?
You can lay out your constructor as follows:
class MyClass {
public function __construct($obj=null) {
if ($obj && $obj instanceof Traversable || is_array($obj)) {
foreach ($obj as $k => $v) {
if (property_exists($this,$k)) {
$this->{$k} = $v;
}
}
}
}
}
This has a serie of drawbacks:
This is inefficient
The variables you create will not show up on any doc software you use
This is the open door to all forms of slackery
However, it also presents the following benefits:
This can be extended pretty safely
It allows you to lazy-implement variables
It also allows you to set private variables, provided that you know their names. It is pretty good in that respect if not abused.
The parameters passed in the parentheses (which can be omitted, by the way, if there aren't any) go to the constructor method where you can do whatever you please with them. If a class is defined, for example, like this:
class WhatEver
{
public $bar;
public function __construct($bar)
{
$this -> bar = $bar;
}
}
You can then give it whatever values you need.
$foo = new WhatEver('baz');
There are a few ways to accomplish this, but each has its own drawbacks.
If your setters return an instance of the object itself, you can chain your methods.
my $foo = new WhatEver();
$foo->setBar("value")->setBar2("value2");
class WhatEver
{
public $bar;
public $bar2;
public function setBar($bar)
{
$this->bar = $bar;
return $this;
}
public function setBar2($bar2)
{
$this->bar2 = $bar2;
return $this;
}
}
However, this doesn't reduce it to one step, merely condenses every step after instantiation.
See: PHP method chaining?
You could also declare your properties in your constructor, and just pass them to be set at creation.
my $foo = new WhatEver($bar1, $bar2, $bar3);
This however has the drawback of not being overtly extensible. After a handful of parameters, it becomes unmanageable.
A more concise but less efficient way would be to pass one argument that is an associative array, and iterate over it setting each property.
The implicit assumption here is that objects have meaningful, presumably public, properties which it is up to the calling code to provide values for. This is by no means a given - a key aspect of OOP is encapsulation, so that an object's primary access is via its methods.
The "correct" mechanism for initialising an object's state is its constructor, not a series of property assignments. What arguments that constructor takes is up to the class definition.
Now, a constructor might have a long series of named parameters, so that you could write $foo = new WhatEver(1, "hello", false, null) but if you want these to act like options, then it could take a single hash - in PHP terms, an Array - as its argument.
So, to answer the question, yes, if your constructor is of the form function __construct(Array $options) and then iterates over or checks into $options. But it's up to the constructor what to do with those options; for instance passing [ 'use_safe_options' => true ] might trigger a whole set of private variables to be set to documented "safe" values.
As of PHP 5.4 (which introduced [ ... ] as an alternative to array( ... )), it only takes a few more character strokes than the Perl version:
$foo = new WhatEver( ['bar' => 'baz'] );
While reading about PHP constructors, I came across the below example on this page.
<?php
class MyClass {
// use a unique id for each derived constructor,
// and use a null reference to an array,
// for optional parameters
function __construct($id="", $args=null) {
// parent constructor called first ALWAYS
/*Remaining code here*/
}
}
I am not able to understand why $id is set to "" and $args to null. When would I use something like this? Why can't we just use function __construct($id, $args) {.
Those are default arguments. When they are not provided by the caller, they will be set to those values. Otherwise, it will be set to the caller's values.
So that both $x = MyClass("12", array(1, 2, 3)) and $y = MyClass() are valid.
Without those default arguments, MyClass() would produce an error.
Those are default argument values.
This means that you can call the constructor without passing any paramaters and then the constructor will add those values to the arguments.
Thus you could create an instance like this:
$mc = MyClass();
So when could this be useful? Well suppose you have a class that usually have the same parameters, let's say you have the class Door which usually is of the type normal. Then you could omit passing that value, but then sometimes you want a Door which is of the type safe then you'd want to pass that. To clarify:
class Door {
private $door_type;
public function __construct($type='normal') {
$this->door_type = $type;
}
}
//Create a 'normal' door
$normal_door = new Door();
$normal_door = new Door('normal'); //the same as above
//Create a 'safe' door
$safe_door = new Door('safe');
This example is obviously not how you'd implement it in the real world but I hope you see the use of it.
As a note, not all languages support this, Java for example does not.
Is it possible to instantiate a class from a string, without declaring another variable before ?
It's usually done writing
$className = 'myClass'
$instance = new $className();
but it could be handy to have it shorter like for example
$instance = new ${'className'}();
The purpose is to have objects created (under condition) inside a loop without use of extra vars...
Edit : $className is dynamic, it is hard coded above to explain the situation
See factory pattern.
class Foo {
static function factory($class, array $args = null) {
return new $class($args);
}
}
// class factoring; returns a new instance of requested class ($className)
Foo::factory($className);
I added optional arguments array if you want to set some class properties.
// pass some values for class constructor
Foo::factory($className, array('arg1' => 1, 'arg2' => 2, 'args3' => 3));
Furthermore, you can build "fluid" interfaces so you can "chain" methods when you use that pattern:
Foo::factory($className)->method1()->method2(array('param' => 'value'))->etc();
where method1(), method2() must return $this (the object itself) to chain multiple method calls in one line.
You could make a factory function (or class/method) that takes a class name as a parameter, and then call it with the result of your dynamic PHP code that generates the string. You might consider it a bit cleaner but it's not going to save you any memory or speed.
class foo { }
function factory($class) { return new $class(); }
foreach (...) {
$instance = factory(<some code that returns the string 'foo'>);
}
It's one extra variable, does it really make much of a difference? The answer is that unless you use eval (which comes with security issues) it isn't possible to do it any shorter than your first example.
I've got a PHPUnit mock object that returns 'return value' no matter what its arguments:
// From inside a test...
$mock = $this->getMock('myObject', 'methodToMock');
$mock->expects($this->any))
->method('methodToMock')
->will($this->returnValue('return value'));
What I want to be able to do is return a different value based on the arguments passed to the mock method. I've tried something like:
$mock = $this->getMock('myObject', 'methodToMock');
// methodToMock('one')
$mock->expects($this->any))
->method('methodToMock')
->with($this->equalTo('one'))
->will($this->returnValue('method called with argument "one"'));
// methodToMock('two')
$mock->expects($this->any))
->method('methodToMock')
->with($this->equalTo('two'))
->will($this->returnValue('method called with argument "two"'));
But this causes PHPUnit to complain if the mock isn't called with the argument 'two', so I assume that the definition of methodToMock('two') overwrites the definition of the first.
So my question is: Is there any way to get a PHPUnit mock object to return a different value based on its arguments? And if so, how?
Use a callback. e.g. (straight from PHPUnit documentation):
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnCallbackStub()
{
$stub = $this->getMock(
'SomeClass', array('doSomething')
);
$stub->expects($this->any())
->method('doSomething')
->will($this->returnCallback('callback'));
// $stub->doSomething() returns callback(...)
}
}
function callback() {
$args = func_get_args();
// ...
}
?>
Do whatever processing you want in the callback() and return the result based on your $args as appropriate.
From the latest phpUnit docs: "Sometimes a stubbed method should return different values depending on a predefined list of arguments. You can use returnValueMap() to create a map that associates arguments with corresponding return values."
$mock->expects($this->any())
->method('getConfigValue')
->will(
$this->returnValueMap(
array(
array('firstparam', 'secondparam', 'retval'),
array('modes', 'foo', array('Array', 'of', 'modes'))
)
)
);
I had a similar problem (although slightly different... I didn't need different return value based on arguments, but had to test to ensure 2 sets of arguments were being passed to the same function). I stumbled upon using something like this:
$mock = $this->getMock();
$mock->expects($this->at(0))
->method('foo')
->with(...)
->will($this->returnValue(...));
$mock->expects($this->at(1))
->method('foo')
->with(...)
->will($this->returnValue(...));
It's not perfect, since it requires that the order of the 2 calls to foo() is known, but in practice this probably isn't too bad.
You would probably want to do a callback in a OOP fashion:
<?php
class StubTest extends PHPUnit_Framework_TestCase
{
public function testReturnAction()
{
$object = $this->getMock('class_name', array('method_to_mock'));
$object->expects($this->any())
->method('method_to_mock')
->will($this->returnCallback(array($this, 'returnTestDataCallback')));
$object->returnAction('param1');
// assert what param1 should return here
$object->returnAction('param2');
// assert what param2 should return here
}
public function returnTestDataCallback()
{
$args = func_get_args();
// process $args[0] here and return the data you want to mock
return 'The parameter was ' . $args[0];
}
}
?>
It is not exactly what you ask, but in some cases it can help:
$mock->expects( $this->any() ) )
->method( 'methodToMock' )
->will( $this->onConsecutiveCalls( 'one', 'two' ) );
onConsecutiveCalls - returns a list of values in the specified order
Pass two level array, where each element is an array of:
first are method parameters, and last is return value.
example:
->willReturnMap([
['firstArg', 'secondArg', 'returnValue']
])
You also can return the argument as follows:
$stub = $this->getMock(
'SomeClass', array('doSomething')
);
$stub->expects($this->any())
->method('doSomething')
->will($this->returnArgument(0));
As you can see in the Mocking documentation, the method returnValue($index) allows to return the given argument.
Do you mean something like this?
public function TestSomeCondition($condition){
$mockObj = $this->getMockObject();
$mockObj->setReturnValue('yourMethod',$condition);
}
I had a similar problem which I couldn't work out as well (there's surprisingly little information about for PHPUnit). In my case, I just made each test separate test - known input and known output. I realised that I didn't need to make a jack-of-all-trades mock object, I only needed a specific one for a specific test, and thus I separated the tests out and can test individual aspects of my code as a separate unit. I'm not sure if this might be applicable to you or not, but that's down to what you need to test.
$this->BusinessMock = $this->createMock('AppBundle\Entity\Business');
public function testBusiness()
{
/*
onConcecutiveCalls : Whether you want that the Stub returns differents values when it will be called .
*/
$this->BusinessMock ->method('getEmployees')
->will($this->onConsecutiveCalls(
$this->returnArgument(0),
$this->returnValue('employee')
)
);
// first call
$this->assertInstanceOf( //$this->returnArgument(0),
'argument',
$this->BusinessMock->getEmployees()
);
// second call
$this->assertEquals('employee',$this->BusinessMock->getEmployees())
//$this->returnValue('employee'),
}
Try :
->with($this->equalTo('one'),$this->equalTo('two))->will($this->returnValue('return value'));