CakePHP makes heavy use of associative arrays for passing large numbers of parameters to functions. I have not really seen this technique outside of PHP and never seen it used to the extent that Cake uses it. I really like this approach because as it seems it would be easier to handle new parameters in future releases of your own code and it's alot more readable than simply a long list of params.
As an example...
function myFunc($params = array('name' => 'rob', 'count' => 5, 'anArray' => array('A string', 5, myObject)))
{
// ...
}
I guess this is similar to using argc/argv, but is a bit easier to read. Does anyone have a list of pros and cons on this method or know of anyone that's written on best practices with this? I've tried just Googling it but "associative array of parameters" brings up pretty much every programming article ever written.
Also, is there even a term for passing parameters this way?
A downside to using named parameters is documenting the parameters with PHPDoc. Many editors/IDEs provide "automatic" documentation that can parse your code and generate generic docblocks.
e.g.
function foo(array $bar, SomeClass $stuff) { returns $magic; }
would produce:
/**
* foo
*
* #param array $bar
* #param SomeClass $stuff
* #return mixed
*/
function foo(array $bar, SomeClass $stuff) { returns $magic; }
If you put all your parameters in a $params array, it would only look like
/**
* foo
*
* #param array $params
* #return mixed
*/
It also adds a lot of additional burden to your developers to have to type the extra code for each parameter. I'd suggest using a mix of the two approaches.
e.g. If you had a function to return an HTML text input element you could have the following method signature:
/**
* formText
*
* #param string $name name of text element
* #param string $value value of text element
* #param array $options additional options for text element
* #return string
*/
function formText($name, $value, $options = array());
Thus, you can easily pass the most common values to the function.
$this->formText('foo', 'Default...');
and if you need additional, less common params you'd use the convenient named parameter syntax:
$this->formText('foo', 'Default...', array(
'class' => 'bold highlighted'
));
otherwise, using only a generic $params array you'd have to type:
$this->formText(array(
'name' => 'foo',
'value' => 'Default...'
));
It is a emulation of using keyword arguments. In python, for example, you can say:
myFunc(name="bob", age=10, gender="male")
Since PHP does not support this syntax, associative arrays are the next-best-thing.
Other high-level languages support named parameters to functions and methods. For instance, in Python you can call:
my_func(name='rob', count=5, an_array=['A string', 5, my_object])
What you are seeing is an attempt to simulate this behavior in PHP. The benefits are obvious.
Flexibility
No need to know order/number of expected parameters
One drawbacks might be that a hash table lookup is required for every method call, but depending on how arguments are handled in PHP, the performance hit may be negligible.
It's the only way to do named parameters in PHP.
It's only useful when you have a large number of arguments, IMO. Otherwise the benefits of an explicitly defined argument signature are the better choice, especially if you use an "intellisense" capable IDE.
This technique is (really) often used in Javascript, with objects (when you are using string as keys, in JS, you are using objects, not arrays) ; for an example, see scriptaculous Draggable.
Now, for a couple of pros and cons that immediatly come to mind :
pro : you can use any number of parameters you want, and still have them named
and there is no "only parameters at the end of the list are optionnal"
con : phpdoc is not fine : it seems there is only one parameter, and there is not much indication on what it does
con (consequence of the previous one) : when you are using an IDE with code-hints, it cannot display names/description for each parameter : you always have to check the documentation.
That single "con" is enough for me : I only use that way of passing parameters if there is no other (reallistic) way.
Related
I think this is quite interesting!!! :).
What I've got?
In the application that I'm using on some level in some objects (doesn't really matter) I get an array, for example:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
I don't have any impact on how and when this array is created. Next, I've got a method:
public function arrayArgument($array) {
$label = isset($array['argument_label']) ? $array['argument_label'] : 'default_label';
$name = isset($array['argument_name']) ? $array['argument_name'] : 'default_name';
$id = isset($array['argument_id']) ? $array['argument_id'] : 'default_id';
// Do something
return 'something';
}
I really hate it. There is no way of proper documentation for the method arguments (as PHPDocumentator work not so well with arrays), and those issets are driving me crazy. Additionally it is a nightmare for someone who will work with this code in the future, when I will already be a "retired nerd".
What I want?
I want to have a function like that:
public function notArrayArgument(
$id='default_id',
$label='default_label',
$name='default_name'
) {
// Do something
return 'something';
}
What I can do?
When I get array, I can change some code, and make my own method run. So I need some kind of solution to get from here:
$array = array(
'argument_label' => 'value_label',
'argument_name' => 'value_name',
'argument_id' => 'value_id'
)
To here:
notArrayArgument('value_id', 'value_label', 'value_name');
Or here:
notArrayArgument($array['argument_id'], $array['argument_label'], $array['argument_name']);
What are the problems?
This is not template like. The number of variables is always different, the names are always different, and sometimes some of them are passed, sometimes not.
It should work really fast...
Calling the method arguments in the right order. Array can be sorted, not sorted or random sorted, while the arguments inside method are always in the same order. The array should be reordered to match the method arguments order, and after that the method should be called.
What I came with?
I've got an idea using reflectionClass. I can check the names of method arguments, get them in order, reorder the array and try to call this method. But this is quite resource eating solution, as reflectionClass is not so fast I think.
Solution using extract? This would be great. But after extract, I need to use exact variables names in code. And I don't know them as every time they are different, and I need an universal approach.
NEW (thx to comment): call_user_func_array(). It is great, but it only works with indexed arrays, so this will be the last but not least step of the possible solution. As the order of arguments is still unknown...
Does this problem have a nice semantic, pragmatic solution?
I read my question once more, and I hope it is clear to understand. If not, please post a comment and I will do my best to describe the problem better.
Kudos for thinking about the maintainer, but I'd argue simplicity is just as important as nice semantics and pragmatism. Consider this: if you had to ask how to write such a pattern, what are the chances that it will be obvious to the reader? I'd much rather come across code where I can just think "Yep, that's clear" than "Oh cool that's a really intricate and clever way of setting array defaults".
With this in mind, it seems to me that an array is being used in a situation more suited to a class. You have an id, a label and a name, which surely represents an entity - exactly what classes are for! Classes make it easy to set defaults and provide PHPDoc on each of their properties. You could have a constructor that simply takes one of your existing arrays and array_merge()s it with an array of defaults. For the reverse conversion, conveniently, casting an object to an array in PHP results in an associative array of its properties.
Try to use classes as George Brighton mentioned.
If you can't for some legacy or library constraint, you will have to use reflection. Don't worry too much about the performance of reflection classes, a lot of frameworks use them to do the request routing.
You can use a function like:
function arrayArgument($object, $method, $array)
{
$arguments = [];
$reflectionMethod = new ReflectionMethod(get_class($object), $method);
foreach ($reflectionMethod->getParameters() as $parameter)
{
$arguments[] = isset($array[$parameter->name]) ? $array[$parameter->name] : $parameter->getDefaultValue();
}
call_user_func_array(array($object, $method), $arguments);
}
So you can do
$instance = new MyClass();
arrayArgument($instance, 'notArrayArgument', ['name' => 'myname']);
This question already has answers here:
PHPDoc for variable-length arrays of arguments
(7 answers)
Closed 5 years ago.
What's the best way to document the elements of an array when it is a parameter to a method? For example, using the PHPDoc headers, I might have something like:
#param array $data
What this doesn't tell me is what elements are mandatory in the array, and what are optional elements. I imagine this should go in the explanation of the method. Something like:
array: $data
============
int $id Required
name $string Required
town $string Optional
/**
* #param array $data
*
* #var $data[id] int, required
* #var $data[name] string, required
* #var $data[town] string, required
*/
This example of using doctrine and zf2 example:
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
* #Form\Attributes({"type":"hidden"})
*/
protected $id;
/**
* #ORM\Column(type="string")
* #Form\Type("Zend\Form\Element\Text")
* #Form\Required({"required":"true"})
* #Form\Filter({"name":"StripTags"})
* #Form\Filter({"name":"StringTrim"})
* #Form\Validator({"name":"StringLength", "options":{"min":"5"}})
* #Form\Options({"label":"First name"})
*/
protected $firstName;
If you have such a complex array with constraints for every single member, I wouldn't use an anonymous array but rather a well defined object. With an array, you can never be sure what it holds, that's somewhat like passing "Object" in e.g. Java, which you rarely would consider a good choice.
However, there is the possibility of a little hinting, when you array contains objects of a certain type as explained here, but that's not a really good answer to your question.
If you really need the parameter as an array, you might document it the way you proposed in the method's description; however, if you use an object as parameter, you'd have additional support in modern IDEs (IntelliSense and so on).
EDIT: I mean, for me the question would be "why would I want to use an anonymous array instead of a self defined type" - and besides simplicity (which will backfire as technical debt later if you maintain and extend your code), I cannot think of a reason, especially compared to what you gain in using a user defined type (self documented code, constraints visible and made explicit by standard methods and so on).
If you just need a dump of data, you might want to go with a simple array, but since you're already thinking about optional and required keys, that screams for a user defined type.
EDIT2: Regarding your comment about if your already have an array as source: I'm not sure whether you need to pass it on as an array or do "mapping" operations as soon as you receive the array (e.g. as $_POST or as return value from some third party library or PHP internal functions or such).
I suppose one could argue that it's not the model's business to interpret data generated by views (e.g. HTML forms which POST data), but rather the controller's resonsibility to react accordingly to the input and transfer the model in the appropriate state. What I mean by this is that you could do something like this if you receive e.g. an array as $_POST:
$customer = new Customer();
$customer->setId($_POST['id']);
$customer->setName($_POST['name']);
$customer->setTown($_POST['town']);
And handle errors as soon as you access the $customer, e.g. throwing exceptions if the name is not set (i.e. $_POST['name'] was empty or such). This way, you use the source array to call setters on the object instead of e.g. passing the array to a factory like Customer::buildByHttpPostData(array $data) and thereby delegating the knowledge of the view's details (names of HTML input tags and such).
Bottom line is, there's no "standard" way to declare required or optional array keys, and of course you can describe those constraints in the method description, but perhaps you can circumvent this by keeping with supported ways like PHPDoc comments on setters or getters.
Of course, there may be better ways to approach the problem, and perhaps somebody comes up with a better answer how to handle it.
To answer the question there is no formal way, try to use a way that you think is most intuitive. I do something similar:
/**
* #param array $data [ int $id, string $name, string $town ]
*/
However I wouldn't use this notation for parameters but rather as return values. In your case I would extract the method arguments to an object and pass that into the method instead:
/**
* #param User $user
*/
public function myMethod( User $user )
{
//...
}
The reason for this is the User object exposes it's properties as an API to other developers, self documenting code!
The other method would be to separate out the array elements into arguments like so:
/**
* #param int $id
* #param string $name
* #param string $town
*/
public function myMethod( $id, $name, $town )
{
//...
}
3 arguments is just about passable but you should start looking for a way to refactor it, like my first suggestion. 4 arguments is generally agreed to be messy and you refactor it.
So here is the basis for building my JSON response back to my JS.
$this->_response['vendor'] = array();
foreach ($_results as $_row) {
$this->_response['vendor'][$_row['id']] = $_row;
}
echo(json_encode($this->_response));
This is fine and builds objects in javascript fine, unless there were no results. In that case, the php sees it as an empty numeric array, instead of an associative array. This then comes down to javascript and converts to an empty array instead of an empty object.
I know I can fix this in a number of ways by checking things, pre-declaring the variables as objects in javascript etc. What I'm wondering is if there is a way to declare an empty associative array in php, or some other way to force json_encode to create an object ("{}") instead.
It's a known limitation of PHP - since it doesn't support JavaScript types natively (eg. objects), decoding and then encoding again may lead to data loss.
I use a workaround like this to keep JSON in tact:
/**
* #param $input
* #return array
*/
public static function safeDecode($input) {
// Fix for PHP's issue with empty objects
$input = preg_replace('/{\s*}/', "{\"EMPTY_OBJECT\":true}", $input);
return json_decode($input, true);
}
/**
* #param array|object $input
* #return string
*/
public static function safeEncode($input) {
return preg_replace('/{"EMPTY_OBJECT"\s*:\s*true}/', '{}', json_encode($input));
}
It's not ideal but it works. Note that you can:
json_decode() to objects, which is default manner. Then empty objects are not lost, but most modern frameworks REQUIRE arrays because most collection manipulation tools work with arrays. Therefore, in most cases it's not a viable option;
You could force json_encode() to encode empty arrays to objects but then you will lose empty arrays - not a nice option either.
Therefore, a work around like mine shown above is perhaps the only option at this moment. In short, you have to extend PHPs json_encode() and json_decode(). Unfortunately PHP doesn't allow built-in function overloading so you have to define functions with different names.
I suppose the most reasonable answer here is that there's probably no point in delivering an empty array or object in the first place. Simple checks for existence in the javascript would then handle the problem.
if (count($_results)) {
$this->_response['vendor'] = array();
foreach ($_results as $_row) {
$this->_response['vendor'][$_row['id']] = $_row;
}
}
echo(json_encode($this->_response));
If a function requires an incoming array to have a specific key/index, is there an eloquent way to express it via comments, specifically PHPDoc?
For example:
/**
* Just an example function
* #param array $arr My Example Array
*/
public function myFunction( $arr ){
if(!array_key_exists('mykey', $arr)){
echo 'Damnit, we needed an array that had mykey as an index!';
}
}
You could write a list of such keys, and place it in the #param description, or put it in the long description of the method's docblock. There is nothing "automated" or "rigid" that phpDocumentor could really do in the documentation here, e.g. link to some other documented element.
No, if you need a specific variable, make it a separate parameter. In that case, if the parameter is empty, an error is thrown.
You can't specify the data-type of array values in PHP. So there is noeed to document that via PHPDoc.
NO there is not. Best to just put it in the comments section.
I was browsing SO and found this hosted code as a recommended way of cutting down on PHP code.
https://github.com/jamierumbelow/codeigniter-base-model
So far, from the methods that I have figured out how to use, I love what it does and how simple it makes things.
However, in the following code:
/**
* Get a single record by creating a WHERE clause by passing
* through a CI AR where() call
*
* #param string $key The key to search by
* #param string $val The value of that key
* #return object
*/
public function get_by() {
$where =& func_get_args();
$this->_set_where($where);
$this->_run_before_get();
$row = $this->db->get($this->_table)
->row();
$this->_run_after_get($row);
return $row;
}
I'm not exactly sure how to make a call to this function.
The description of what it does is exactly what I want to do.
The #params say it takes in a key and value pair for the WHERE block but I don't see any function inputs in the method signature.
Help, please?
As I'm noticing with a lot of CI code, it's strange and maintenance un-friendly.
PHP functions can accept n or more arguments (where n is the number of arguments defined in the signature)
The code makes use of func_get_args() which returns an array of arguments.
The array of arguments is then passed to the _set_where() method which passes either one or two items to the db->where() method.
A more descriptive method signature would have been
public function get_by($key, $val = null)
For future reference, and like Phil mentioned, the *_by methods pass the value through to the db->where method. This means you can use it in a variety of methods:
$row = $this->model->get_by('key', $value);
Or using an array for multiple WHERE conditions:
$row = $this->model->get_by(array('key' => $value, 'other_key !=' => $value));
Or just the string (don't forget to escape your values!):
$row = $this->model->get_by('some_column = ' . $this->db->escape($value));
Since this question was asked, I've thoroughly updated the documentation so now it should all be a little clearer. Hope this helps.