10

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.

thom
  • 103
  • 1
  • 5
  • 2
    Pass a single array which contains each parameter as an array element. – Marc B Jul 04 '11 at 19:04
  • @MarcB: Why don't you post that as an answer? – Felix Kling Jul 04 '11 at 19:04
  • 2
    Usually this is an indicator that the method is too complex/powerful. You should break the functionality down. – Felix Kling Jul 04 '11 at 19:05
  • 1
    There's no silver bullet for this. It's very specific on the method. An array is a possibility, but not always suitable. Why don't you post the method in full, or at least the signature and describe (in detail) what the individual parameters do, and what the method does of course! **Edit**: "How?" By expanding your question so we can help ;) – phant0m Jul 04 '11 at 19:07
  • @thom I recommend you reading "Refactoring", it can answer many questions on how to make the method simpler. I've posted below one of the techniques, google for it to see examples. – Maxim Krizhanovsky Jul 04 '11 at 19:13
  • 3
    Ah, I can't downvote a commentary. Only upvote - it's wrong. So, Marc B, -1 for your comment. – OZ_ Jul 04 '11 at 19:17
  • Your edit doesn't really clarify a lot to me, I had hoped for a more detailed explanation, also some real code and the *real* method signature, not only hinted at by `...` – phant0m Jul 04 '11 at 19:32

6 Answers6

17

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;

OZ_
  • 12,492
  • 7
  • 50
  • 68
6

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
    }
}
webjawns.com
  • 2,300
  • 2
  • 14
  • 34
2

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
}
Trevor
  • 11,269
  • 2
  • 33
  • 40
  • You can always pass any arguments to a method... having an array does not make it easier to use it "incorrectly". – phant0m Jul 04 '11 at 19:10
  • 1
    This one is ok for optional parameters, but in my opinion it's not suitable for required parameters, as you have to check the body of the method to see what were the required keys. Also, it makes body longer, as you have to check if the given parameter was passed or not (and to provide the default values) – Maxim Krizhanovsky Jul 04 '11 at 19:11
  • @phant0m If you have a defined set of REQUIRED parameters in a function then you know that it requires those parameters. By using an array, you can pass in an incorrect number of parameters that the function may require to perform correctly. Like I said, you will have to create a validation method of said required parameters so you can throw an exception if they are incorrect. Therefore, it is easier to misuse. Especially if a client is using the code and is unfamiliar with it. – Trevor Jul 04 '11 at 19:17
  • @Darhazer I agree. This makes for some rough code and likely some debugging issues in the future. Required parameters should be passed in seperatly. – Trevor Jul 04 '11 at 19:20
  • 1
    It's not only bad because you don't know which arguments are optional and which are not. It's bad because you have to keep in memory all names and datatypes of fields of that array(!), *and* because IDE will not be able to give you autocomplete-suggestions. – OZ_ Jul 04 '11 at 19:22
  • @trevor: True, but PHP will only issue a warning, whereas accessing an array via an undefined index will trigger a Notice, I guess, but the call will succeed in both instances. – phant0m Jul 04 '11 at 19:30
  • Yes, but as indicated by @OZ_, it is much more suitable for optional arguments. – phant0m Jul 04 '11 at 19:41
  • @Trevor, passing `array of parameters` it's always bad practice. – OZ_ Jul 04 '11 at 19:42
2

Introduce Parameter Object

Maxim Krizhanovsky
  • 26,265
  • 5
  • 59
  • 89
  • 1
    Already -2 (and mine +1). @Darhazer, your answer is only one which is correct, but, maybe, you can write something more than 3 words in a link? :) – OZ_ Jul 04 '11 at 19:29
  • @OZ_, when parameters are $foo, $bar and $lorem is hard to explain how to refactor them ;) You know - the question contains half of the answer, or the answer is in the same quality as the question. Anyway, thanks for the critics, I agree that some explanation would be useful in order to motivate the reader to read more about this refactoring :) – Maxim Krizhanovsky Jul 04 '11 at 19:33
2

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
    }
}
?>
Leonard
  • 3,012
  • 2
  • 31
  • 52
0

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.

Mridul Kashatria
  • 4,157
  • 2
  • 18
  • 15