42

When you define a function in a namespace,

namespace foo {
    function bar() { echo "foo!\n"; }
    class MyClass { }
}

you must specify the namespace when calling it from another (or global) namespace:

bar();          // call to undefined function \bar()
foo\bar();      // ok

With classes you can employ the "use" statement to effectively import a class into the current namespace [Edit: I thought you could "use foo" to get the classes, but apparently not.]

use foo\MyClass as MyClass;
new MyClass();  // ok, instantiates foo\MyClass

but this doesn't work with functions [and would be unwieldy given how many there are]:

use foo\bar as bar;
bar();          // call to undefined function \bar()

You can alias the namespace to make the prefix shorter to type,

use foo as f;   // more useful if "foo" were much longer or nested
f\bar();        // ok

but is there any way to remove the prefix entirely?

Background: I'm working on the Hamcrest matching library which defines a lot of factory functions, and many of them are designed to be nested. Having the namespace prefix really kills the readability of the expressions. Compare

assertThat($names, 
    is(anArray(
        equalTo('Alice'), 
        startsWith('Bob'), 
        anything(), 
        hasLength(atLeast(12))
    )));

to

use Hamcrest as h;
h\assertThat($names, 
    h\is(h\anArray(
        h\equalTo('Alice'), 
        h\startsWith('Bob'), 
        h\anything(), 
        h\hasLength(h\atLeast(12))
    )));

3 Answers 3

54

PHP 5.6 will allow to import functions with the use keyword:

namespace foo\bar {
    function baz() {
        echo 'foo.bar.baz';
    }
}

namespace {
    use function foo\bar\baz;
    baz();
}

See the RFC for more information: https://wiki.php.net/rfc/use_function

Sign up to request clarification or add additional context in comments.

1 Comment

I just built the 5.6.0-dev on windows and tried it out. Seems to work great, although you have to import each function individually.
9

By adding the helper hacks mentioned below, you can import everything from Hamcrest namespace to current namespace by calling:

import_namespace('Hamcrest', __NAMESPACE__);

Here are the hacks, function_alias works like http://www.php.net/manual/en/function.class-alias.php except if works on functions:

function function_alias ($original, $alias) {

  $args = func_get_args();
  assert('count($args) == 2', 'function_alias(): requires exactly two arguments');
  assert('is_string($original) && is_string($alias)', 'function_alias(): requires string arguments');

  // valid function name - http://php.net/manual/en/functions.user-defined.php
  assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][\\\\\\\\a-zA-Z0-9_\x7f-\xff]*$/\', $original) > 0',
"function_alias(): '$original' is not a valid function name");
  assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][\\\\\\\\a-zA-Z0-9_\x7f-\xff]*$/\', $alias) > 0',
    "function_alias(): '$alias' is not a valid function name");

  $aliasNamespace = substr($alias, 0, strrpos($alias, '\\') !== false ? strrpos($alias, '\\') : 0);
  $aliasName = substr($alias, strrpos($alias, '\\') !== false ? strrpos($alias, '\\') + 1 : 0);
  $serializedOriginal = var_export($original, true);

  eval("
    namespace $aliasNamespace {
      function $aliasName () {
        return call_user_func_array($serializedOriginal, func_get_args());
      }
    }
  ");

}

In combination with namespace importer:

function import_namespace ($source, $destination) {

  $args = func_get_args();
  assert('count($args) == 2', 'import_namespace(): requires exactly two arguments');
  assert('is_string($source) && is_string($destination)', 'import_namespace(): requires string arguments');

  // valid function name - http://php.net/manual/en/functions.user-defined.php
  assert('preg_match(\'/^([a-zA-Z_\x7f-\xff][\\\\\\\\a-zA-Z0-9_\x7f-\xff]*)?$/\', $source) > 0',
    "import_namespace(): '$destination' is not a valid namespace name");
  assert('preg_match(\'/^([a-zA-Z_\x7f-\xff][\\\\\\\\a-zA-Z0-9_\x7f-\xff]*)?$/\', $destination) > 0',
    "import_namespace(): '$source' is not a valid namespace name");

  foreach(get_declared_classes() as $class)
    if (strpos($class, $source . '\\') === 0)
      class_alias($class, $destination . ($destination ? '\\' : '') . substr($class, strlen($source . '\\')));

  $functions = get_defined_functions();
  foreach(array_merge($functions['internal'], $functions['user']) as $function)
    if (strpos($function, $source . '\\') === 0)
      function_alias($function, $destination . ($destination ? '\\' : '') . substr($function, strlen($source . '\\')));
}

Comments

1

I don't know an elegant solution, but...

You can create wrapper functions that encapsulate the functions in the external namespace. This will let you keep your code readability...

function assertThat($x, $y) { return h\assertThat($x, $y); }

1 Comment

The existing functions are already convenience wrappers that call the real static factory methods. I could provide a duplicate copy of this module without the namespace and let the user decide which they wanted to import. The effect would be the same and I'd bet fairly easy to automate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.