PHP讨论 - 将参数作为数组或“配置”类传递

First, if this question doesn't belong here (and there is a better place for it), let me know.

Second, I'm asking this question about "code quality", not "performance".

Third, This isn't really related to PHP but as each laguage have their "Good Practices", I prefer specify it.

Finally, if there is a similar question, sorry about that, I didn't find it.

Now the Question:

Is it better to have a method parameter as array or as a "Configuration" class ?

For example, I'm working with Doctrine and in my Reporsitory, i often have one method that accepts "criteria" and another method prepareCriteria that use the $criteria array to build the QueryBuilder.

And the more I work with array in these cases, the more I wonder if it wouldn't be better to have some CriteriaBuilder (or CriteriaConfiguration or whatever) to completely control the typing of the parameters passed and not rely on simple string key of an array.

What are your thoughts about that ?

ps: I don't really knwo what tags can be fitted to this sort of question. I added doctrine as I'm talking about it as example, but if you have more precise tags that could be used, let me know.

The probably best practice in PHP is to use verbose associative arrays and string assertions with only a small overhead when assertions are disabled on production systems or as of PHP7 even expections with zero cost. It's annoying when writing code but readable on later code reviews and also discovering errors when testing code at development time.

Just to show an example:

php.ini

zend.assertions  = 1
assert.warning   = 1
assert.exception = 1
assert.bail      = 1

PHP

function foobar(array $config)
{    
  assert
  (
    (function($keys_allowed, $keys_expected)use($config)
    {
      // do not stop execution until all issues are output
      $opt_expt = assert_options(ASSERT_EXCEPTION, 0);
      $opt_bail = assert_options(ASSERT_BAIL     , 0);

      $ok = 1;

      $ok &= assert( empty( $diff = array_diff_key(array_fill_keys($keys_expected, 1), $config) ),
                     new AssertionError('Missing mandatory keys: (' . join(', ', array_keys($diff)) . ')'));

      $ok &= assert( empty( $diff = array_diff_key($config, $keys_allowed) ),
                     new AssertionError('Disallowed keys: (' . join(', ', array_keys($diff)) . ')'));

      $ok &= assert( empty( $diff = array_filter
                                    ( $config,
                                      function($v, $k)use($keys_allowed)
                                      {
                                        return isset($keys_allowed[$k]) && $keys_allowed[$k] !== gettype($v);
                                      },
                                      ARRAY_FILTER_USE_BOTH)
                                    ),
                     new AssertionError('Type error on items: (' . join(', ', array_keys($diff)) . ')'));

      //restore configured behaviour    
      assert_options(ASSERT_EXCEPTION, $opt_expt);
      assert_options(ASSERT_BAIL     , $opt_bail);
      return $ok;
    })
    (
      [ 'foo' => 'string', 'bar' => 'integer', 'baz' => 'boolean' ],   [ 'baz' ]
    )
    , new AssertionError('Arguments assertion in function `' . __FUNCTION__ . '` failed.')
  );


  // foobar function stuff
  echo 'Ok!', PHP_EOL;
}

// test
foobar(['foo' => '1',              'baz' => true                     ] );  // Ok!
foobar([              'bar' => 1 , 'baz' => true                     ] );  // Ok!


foobar(['foo' => 1  ,              'baz' => true                     ] );  // Warning: type ; Fatal error
foobar(['foo' => '1', 'bar' => 1                                     ] );  // Warning: missing ; Fatal error
foobar(['foo' => 1  , 'bar' => '1'               , 'forbidden' => 'x'] );  // Warnings: missing, disallowed, type ; Fatal error

The outer assertion throws a fatal error stopping the execution while the inner assertions throw non-breaking warnings.

You can easily transform the anonymous function into a named function taking the used variable as additional parameter.