PHP Good practices - Methods with too many parameters - php

I've made a class and method just for search things on my website. It has too many parameters, the search parameters. My controller grabs data from forms and then pass to the model.
public function search($name, $age, $foo, ... $bar, $lorem) {
Are there any tips of this kind of method? Maybe a good practice about method with too much parameters.
Thanks.
EDIT:
parameters are for the search...
$name should search people with value of $name
$age should search people with value of $age
and so on...
something like the SQL Where clause.
Thanks again.

Darhazer and Zanathel already gave good answers, and I just want to show you one thing: setters with fluent interface. Only when all parameters are optional.
$finder->
setName($name)->
setAge($age)->
setFoo($foo)->
setBar($bar)->
setLorem($lorem)->
search();
or
$query = new SearchQuery($required_argument);
$query->setAge($optional)->setLorem($optional);
$finder->search($query);
to create fluent interface, just write in setter's body return $this;

I like using arrays for functions that might/have many parameters. This sort of approach allows for near infinite expansion of parameters, and is more straightforward and better than using something like func_get_args().
public function search(array $options = array())
{
$defaults = array(
'name' => null,
'age' => null,
'order' => null,
'limit' => null,
'offset' => null,
);
$options = array_merge($defaults, $options);
extract($options);
$select = $this->select();
if (!is_null($name)) {
$select->where('name = ?', $name);
}
if (!is_null($age)) {
$select->where('age = ?', $age, Zend_Db::INT_TYPE);
}
if (!is_null($order)) {
$select->order($order);
}
if (!is_null($limit) || !is_null($offset)) {
$select->limit($limit, $offset);
}
$results = $this->fetchAll($select);
return $results;
}
...or you can use an object oriented approach:
class SearchQuery
{
public function __construct(array $options = null)
{
if (!is_array($options)) {
return;
}
if (array_key_exists('name', $options)) {
$this->setName($options['name']);
}
if (array_key_exists('age', $options)) {
$this->setAge($options['age']);
}
}
public function setName($name)
{
if (!is_string($name)) {
throw InvalidArgumentException('$name must be a string');
}
$this->_name = $name;
return $this;
}
public function setAge($age)
{
if (!is_numeric($age) || $age <= 0) {
throw new InvalidArgumentException('$age must be a positive integer');
}
$this->_age = $age;
return $this;
}
}
// then you can use dependency injection in your main search class
class SearchService
{
public function search(SearchQuery $query)
{
// search
}
}

Introduce Parameter Object

You could pack things into a key=>value based array
Example:
$params = array("Name"=>"Bob", "Age"=32.....);
Class->search($params);
public function search($params) {
// access keys
}
This is a little bit weary because the method could be used incorrectly very easily since any array could be passed in. Some validation may be required in some other method call to verify the arrays contents.
Edit: Since there has been some debate in the comments.... here is another way to do this
Create a new class! A user class that contains name age and such and whatever other demographics. You'll probably use those in other places anyway.
Pass the object or objects in as arguments
$object = new Object();
SearchClass->search($object)
public function search(Object $object){
// Do junk here
}

I'd use the closest simile to value objects, where you pass one object that contains all the necessary parameters as properties to the function.
<?php
class FilterVO {
public $id;
public $name;
// etc ...
}
class SomeCollection {
public function FilterResults(FilterVO $prefs) {
// got all the goodies in here
}
}
?>

make it so that it accepts an array as parameter with many name-value pairs. Then you can use extract($paramsArray) to make all names in array as $variables.

Related

How can I dynamically invoke all instance methods starting with a given prefix?

i have a class and i want to call dynamicly all functions starting by default name:
class social_button
{
public function __construct()
{
[...]
}
private function social_facebook()
{[...]}
private function social_instagramm();
{[...]}
private function social_twitter();
{[...]}
[and so on]
}
My matter is, that i wont write all time:
$this->social_facebook();
$this->social_twitter();
...
because it could/will be an endless list.
So here is my questions:
Is there a way to call all functions generic/dynamic starting with "social"?
Like: $this->social_*();
(The " * " is something like a placeholder, which contains an unlimited number of chars)
Sorry for my bad english and much thanks to all answers.
Best regards
You can build the method name with the string concatenation:
$service = 'facebook';
$this->{'social_' . $service}();
or
$service = 'social_facebook';
$this->$service();
If you wan to call all of them, go with:
$services = ['facebook', 'twitter'];
foreach ($services as $service) {
$this->{'social_' . $service}();
}
Edit: See the answer by localheinz below for a better method, using reflection. get_class_methods() will only return public methods.
Building off hsz's answer:
You can get the list of a class' methods using get_class_methods(). Then you can loop through the results, and call the method if it starts with "social_".
// Get the list of methods
$class_methods = get_class_methods("social_button");
// Loop through the list of method names
foreach ($class_methods as $method_name)
{
// Are the first 7 characters "social_"?
if (substr($method_name, 0, 7) == "social_")
{
// Call the method
$this->{$method_name}();
}
}
The problem with the accepted answer is that it will not work with the example posted with the question. get_class_methods() returns only public methods, but the methods in question are marked as private.
If you want to determine all methods, use reflection instead:
class social_button
{
private function social_facebook()
{
return 'Facebook';
}
private function social_instagram()
{
return 'Instagram';
}
private function social_twitter()
{
return 'Twitter';
}
public function all()
{
$reflection = new \ReflectionObject($this);
$prefix = 'social_';
// filter out methods which do not start with the given prefix
$methods = array_filter($reflection->getMethods(), function (\ReflectionMethod $method) use ($prefix) {
return 0 === strpos($method->getName(), $prefix);
});
// invoke all methods and collect the results in an array
$results = array_map(function (\ReflectionMethod $method) {
$name = $method->getName();
return $this->$name();
}, $methods);
return $results;
}
}
$button = new social_button();
var_dump($button->all());
For reference, see:
http://php.net/manual/en/class.reflectionobject.php
http://php.net/manual/en/class.reflectionmethod.php
http://php.net/manual/en/function.array-filter.php
http://php.net/manual/en/function.array-map.php
For an example, see:
https://3v4l.org/qTkOM

PHP Last Object of Method Chaining

In PHP using method chaining how would one go about supplying a functional call after the last method being called in the chain?
Also while using the same instance (see below). This would kill the idea of implementing a destructor.
The end result is a return value and functional call of private "insert()" from the defined chain properties (of course) without having to call it publicly, no matter of the order.
Note, if I echo (__toString) the methods together it would retrieve the final generated unique code which is normal behavior of casting a string.
Example below:
class object
{
private $data;
function __construct($name) {
// ... some other code stuff
}
private function fc($num) {
// some wicked code here
}
public function green($num) {
$this->data .= fc($num*10);
return $this;
}
public function red($num) {
$this->data .= fc($num*25);
return $this;
}
public function blue($num) {
$this->data .= fc($num*1);
return $this;
}
// how to get this baby to fire ?
private function insert() {
// inserting
file_put_content('test_code.txt', $this->data);
}
}
$tss = new object('index_elements');
$tss->blue(100)->green(200)->red(100); // chain 1
$tss->green(0)->red(100)->blue(0); // chain 2
$tss->blue(10)->red(80)->blue(10)->green(0); // chain 3
Chain 1, 2, and 3 would generated an unique code given all the values from the methods and supply an action, e.g. automatically inserting in DB or creating a file (used in this example).
As you can see no string setting or casting or echoing is taking place.
You could keep a list of things that needs to be initialised and whether they
have been so in this instance or not. Then check the list each time you use
one of the initialisation methods. Something like:
class O {
private $init = array
( 'red' => false
, 'green' => false
, 'blue' => false
);
private function isInit() {
$fin = true;
foreach($this->init as $in) {
$fin = $fin && $in;
}
return $fin;
}
public function green($n) {
$this->init['green'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function red($n) {
$this->init['red'] = true;
if($this->isInit()) {
$this->insert();
}
}
public function blue($n) {
$this->init['blue'] = true;
if($this->isInit()) {
$this->insert();
}
}
private function insert() {
echo "whee\n";
}
}
But personally I think this would be more hassle then it's worth. Better imo
to expose your insert method and let the user of you code tell when the
initialisation is finished. So something that should be used like:
$o->red(1)->green(2)->blue(0)->insert();
-update-
If it's the case that it's impossible to predict what functions need to be called
you really do need to be explicit about it. I can't see a way around that. The reason
is that php really can't tell the difference between
$o1 = new A();
$o2 = $o1->stuff();
and
$o2 = (new A())->stuff();
In a language that allows overloading = I guess it would be possible but really
really confusing and generally not a good idea.
It is possible to move the explicit part so that it's not at the end of the call
chain, but I'm not sure if that would make you happier? It would also go against
your desire to not use another instance. It could look something like this:
class O {
public function __construct(InitO $ini) {
// Do stuff
echo "Whee\n";
}
}
class InitO {
public function red($n) {
return $this;
}
public function green($n) {
return $this;
}
public function blue($n) {
return $this;
}
}
$o = new O((new InitO())->red(10)->red(9)->green(7));
You can of course use just one instance by using some other way of wrapping
but the only ways I can think of right now would look a lot uglier.
Im with PeeHaa, this makes no sense! :)
Only chance to have something magically happen after the last chain was used (without being able to look into the future) is a Destructor/Shutdown function OR a manually cast/call to insert()
You can also decide to implement this statically without using objects.
<?php
class Object
{
private static $data;
public static function set($name)
{
// ... some other code stuff
}
private static function fc($num)
{
// some wicked code here
}
public static function green($num)
{
self::$data .= self::fc($num*10);
return new static;
}
public static function red($num)
{
self::$data .= self::fc($num*25);
return new static;
}
public static function blue($num) {
self::$data .= self::fc($num*1);
return new static;
}
// how to get this baby to fire ?
public static function insert()
{
// inserting
file_put_content('test_code.txt', self::$data);
}
}
//$tss = new object('index_elements');
$Object::set('index_elements')->blue(100)->green(200)->red(100)->insert(); // chain 1
$Object::set('index_elements')->green(0)->red(100)->blue(0)->insert(); // chain 2
$Object::set('index_elements')->blue(10)->red(80)->blue(10)->green(0)->insert(); // chain 3
?>
Ok let's see a code example
<?php
// map dummy class
class map
{
// __call magic method
public function __call($name, $args)
{
return $this;
}
}
// now we chain
$map = new map;
// let's find me
$map->start('here')
->go('right')
->then()
->turn('left')
->and('get water')
->dontEat()
->keep('going')
->youShouldSeeMe('smiling');
here we don't know what the last method would be and we need to trigger a kinda operation or event once we hit the end.
According to data structure we can call this the LIFO stack. (Last in first out)
so how did i solve this on PHP?
// i did some back tracing
... back to the __call function
function __call($name, $args)
{
$trace = debug_backtrace()[0];
$line = $trace['line'];
$file = $trace['file'];
$trace = null;
$getFile = file($file);
$file = null;
$getLine = trim($getFile[$line-1]);
$line = null;
$getFile = null;
$split = preg_split("/(->)($name)/", $getLine);
$getLine = null;
if (!preg_match('/[)](->)(\S)/', $split[1]) && preg_match('/[;]$/', $split[1]))
{
// last method called.
var_dump($name); // outputs: youShouldSeeMe
}
$split = null;
return $this;
}
And whoolla we can call anything once we hit the bottom.
*(Notice i use null once i am done with a variable, i come from C family where we manage memory ourselves)
Hope it helps you one way or the other.

Accessing private/protected properties of an object in anonymous function in PHP

I'm trying to dump elements of an object's private property through an anonymous function - of course I could achieve this in any number of other ways, but this highlights a PHP conundrum I can't solve off the top of my head, short of $foo = $this and using $foo - but THAT won't give me the private stuff, so... suggestions ?
Sample code:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$values = array_map(
function($name) use ($this) { return $this->payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
$mc = new MyClass();
print $mc->toCSV();
I believe there is absolutely no way to do directly what you propose.
However, you can work around it either by making the anonymous method a class method (this is not what you asked for, but it could be a practical solution) or pulling everything you need out of $this explicitly and passing the extracted values into the function:
class MyClass
{
private $payload = Array( 'a' => 'A element', 'b' => 'B element');
static $csvOrder = Array('b','a');
public function toCSV(){
$payload = $this->payload;
$values = array_map(
function($name) use ($payload) { return $payload[$name]; },
self::$csvOrder
);
return implode(',',$values);
}
}
You can hack around the limitation by creating a wrapper that utilizes Reflection to allow you to access all properties and methods. You can use it like this then:
$self = new FullAccessWrapper($this);
function () use ($self) { /* ... */ }
Here a sample implementation of the wrapper, taken from here:
class FullAccessWrapper
{
protected $_self;
protected $_refl;
public function __construct($self)
{
$this->_self = $self;
$this->_refl = new ReflectionObject($self);
}
public function __call($method, $args)
{
$mrefl = $this->_refl->getMethod($method);
$mrefl->setAccessible(true);
return $mrefl->invokeArgs($this->_self, $args);
}
public function __set($name, $value)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
$prefl->setValue($this->_self, $value);
}
public function __get($name)
{
$prefl = $this->_refl->getProperty($name);
$prefl->setAccessible(true);
return $prefl->getValue($this->_self);
}
public function __isset($name)
{
$value = $this->__get($name);
return isset($value);
}
}
Obviously the above implementation doesn't cover all aspects (e.g. it can't use magic properties and methods).
As you said yourself, it is private and therefore in accessible.
You can:
Pass $this->payload as a parameter to the anonymous function.
Create a method in the class and use it instead.

How can I make different methods in my form-builder class?

If I create a new PHP class e.g. to simplify form building (yes I know there are some out there) but I am also trying to learn about classes so pls. be patient - thanks ...
OK I create a new class in the usual way
class newform { class details in here }
add a construct function
public function __construct() { function in here }
I can then call that class again in the usual way
$newform = new newform();
so far so good .... (for me anyhow).
Now I can add some args to the function like so
public function __construct($args) { function in here }
and inside the function "go through" the args - which in my case is an array so written like this
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
I can do all that but how do I "add further functions" What I mean here is at the moment I have to declare a new class for every input: i.e.
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
$newform->textarea;
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
$newform->textinput;
That seems "very" long winded to me and therefore wrong.
How do you do something like this (syntax I know is wromg) where textarea and textinput are created in the class a bit like this (but without the args) $this->textarea = '<textarea></textarea>';
$newform = new newform();
$newform->textarea($args);
$newform->textinput($args);
$newform->textarea($args);
What I mean is what additional function/s do you put into the class to allow you to firstly declare the class ($newform = new newform();) then pass $args to items within the class so you can do "something" like above?
Hope I am explaining myself.
If the arguments in the parameter array are related to individual form elements, move the parameter to a new function instead of the passing it to the class constructor. Like so:
class newform {
public function __construct() { }
public function make_textarea(array $args) {
/* do stuff here */
return $formatted_textarea; // a string like '<textarea></textarea>'
}
public function make_input(array $args) {
/* do stuff here */
return $formatted_input; // a string like '<input />'
}
}
Then in your template:
$form = new newForm;
echo $form->make_textarea(array('arg1' => 'val1', 'arg2' => 'val2'));
echo $form->make_input(array('arg1' => 'val3', 'arg2' => 'val4'));
Note: I'm not doing ($args = array('arg1'=> when calling the method. Assigning the array to a variable is not necessary.
Note: Notice the array type hinting: make_textarea(array $args). That's only there to make sure an array is passed to the method. If anything else is passed to the method--a string for example--a Fatal Error will be thrown.
Update - How to use a private method
class Example {
public function do_something(array $args) {
$result = $this->private_method($args);
return $result;
}
private function private_method(array $args) {
/* do stuff here */
return $formatted_args;
}
}
It isn't long-winded to declare functions for each type of tag you want to generate. There are a finite number of tags, and rather than relying on dynamically intercepting function calls via __call you're better off simply defining the methods.
Move most of the internal implementation for each type of tag can be moved to a private method for generating generic HTML tags. Not all form elements share any internal implementation though; tags like <input type="password" /> and <input type="text" /> are obvious candidates for a shared implementation, while <select> elements will require special handling.
The following should give you an idea. When you build your own, don't forget to escape htmlspecialchars where appropriate:
class Form_helper {
// pass boolean false for $contents to build a self-closing "<input />"-style tag
private function html_tag($name, $contents, array $attributes = array() {
$tag = "<$name";
foreach ($attributes as $key => $value) {
$tag .= " $key=\"$value\"";
}
if ($contents === false) {
// self-closing
$tag .= " />";
} else {
$tag .= ">$contents</$name>";
}
return $tag;
}
public function textarea($contents, array $attributes = array()) {
return $this->html_tag('textarea', $contents, $attributes);
}
public function input(array $attributes = array()) {
return $this->html_tag('input', false, $attributes);
}
public function select(array $options) {
// options contains "value"=>"contents" mappings, for production
// option tags in the form <option value="value">contents</option>
$option_tags = '';
foreach ($options as $value => $content) {
$option_tags .= $this->html_tag('option', $content, array('value' => $value));
}
return $this->html_tag('select', $option_tags);
}
}
First of all, you don't have to do this:
$newform = new newform($args = array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
This will suffice:
$newform = new newform(array('arg1'=>'arg1 val','arg2'=>'arg2 val'));
That is, if you want to pass an array as the first argument. Normally, you would do something like this instead:
class newform {
public function __construct($arg1, $arg2) {
// method body here
}
}
$form = new newform('arg1 val', 'arg2 val');
Now, you must keep in mind that a constructor (__construct) is just like another method. So you can do this:
class newform {
public function __construct() {
// method body here
}
public function textarea($name) {
echo '<textarea name="'.$name.'"></textarea>';
}
public funciton textinput($name) {
echo '<input type="text" name="'.$name.'"/>';
}
}
$form = new newform;
$form->textarea('foo');
$form->textinput('bar');
Outputs:
<textarea name="foo"></textarea>
<input type="text" name="bar"/>
I'm not sure what you mean, but my guts tell me that what you need are so-called magic methods:
magic methods / overloading / manual
Best
Raffael

how can I get around no arrays as class constants in php?

I have a class with a static method. There is an array to check that a string argument passed is a member of a set. But, with the static method, I can't reference the class property in an uninstantiated class, nor can I have an array as a class constant.
I suppose I could hard code the array in the static method, but then if I need to change it, I'd have to remember to change it in two places. I'd like to avoid this.
You can create a private static function that will create the array on demand and return it:
class YourClass {
private static $values = NULL;
private static function values() {
if (self::$values === NULL) {
self::$values = array(
'value1',
'value2',
'value3',
);
}
return self::$values;
}
}
I put arrays in another file and then include the file wherever I need it.
I am having a really really hard time understanding your question. Here is essentially what I understood:
I need to maintain a proper set, where
no two elements are the same.
PHP does not have a set type, not even in SPL! We can emulate the functionality of a set but any solution I can think of is not pleasant. Here is what I think is the cleanest:
<?php
class Set {
private $elements = array();
public function hasElement($ele) {
return array_key_exists($ele, $elements);
}
public function addElement($ele) {
$this->elements[$ele] = $ele;
}
public function removeElement($ele) {
unset($this->elements[$ele]);
}
public function getElements() {
return array_values($this->elements);
}
public function countElements() {
return count($this->elements);
}
}
Example usage:
<?php
$animals = new Set;
print_r($animals->getElments());
$animals->addElement('bear');
$animals->addElement('tiger');
print_r($animals->getElements());
$animals->addElement('chair');
$animals->removeElement('chair');
var_dump($animals->hasElement('chair'));
var_dump($animals->countElements());

Categories