Variable Class Names ignore "use" - php

From other posts, it appears that if you have namespaces defined and want to dynamically create an object in another namespace, you have to construct a string and use that in the new call. However, I'm getting a weird behavior. It appears that this method does not work going across namespaces.
User.php:
namespace application\models;
class User {
public function hello() {
echo "Hello from User!";
}
}
Controller.php:
namespace application\controllers;
use application\models;
require('User.php');
$userStr = 'models\\User';
//$userOne = new $userStr(); //Doesn't work. Gets a "Class 'models\User' not found" error
$userOne = new models\User(); //Works fine
$userStr = '\\application\\models\\User';
$userTwo = new $userStr(); //Works fine
$userOne->hello();
$userTwo->hello();
Any idea why when using a variable for the class name, I need to use the fully qualified namespace when it's in a variable, but hard coded, I can leverage the "use" command?

You can not import with use into variable classnames. That is a limitation of PHP.
See as well the related questions:
Expanding PHP namespace alias to full namespace string
Can't get constant from dynamic class using namespaces

Related

Class not found when calling another class name in namespace

When I create a class object dynamically I don't understand why it throws class not found. However, if I will remove namespace App\Controls and use App\Controls\one, it worked perfectly example code:
namespace App\Controls;
use App\Controls\one;
class one {
public static function test_one($className, $object) {
return $object((new $className));
}
}
class two {
public function language($lang) {
echo 'I love '.$lang;
}
}
one::test_one('two', function($table) {
$table->language('PHP');
});
from this structure I want to call class two depends on the value I input. I also include use App\Controls\two but still not working, but that's not the case because supposedly I will not add another use App\Controls\className just to call create another class object and it's not possible from PHP.
In PHP, namespaces are resolved when the code is compiled, not when it is run.
So, when you write this:
namespace MyNamespace;
use MyOthernamespace\SomeClass;
$foo = new Foo;
$bar = new SomeClass;
The compiler looks at the current namespace, and the class names you've "imported", and compiles the code as though you'd written this:
$foo = new MyNamespace\Foo;
$bar = new MyOthernamespace\SomeClass;
(Note that you don't need to use anything in the current namespace; that's assumed as the default prefix for everything.)
In your code, the compiler doesn't know that the 'two' is going to be used as a class name, so it doesn't change it; so when the dynamic code runs, it comes out literally as:
return $object((new two));
Which won't work - there isn't a class called two, only one called App\Controls\two.
The solution to this is the magic ::class syntax, which tells the compiler to expand something like it would for a class name, and then turn it into a string. It's important to know that despite the name, it doesn't actually care if the thing it's expanding is a class, or exists at all, it just expands it and assumes you know what you're doing with it.
So if you write this:
namespace App\Controls;
one::test_one(two::class, function($table) {
$table->language('PHP');
});
It will be expanded by the compiler to this:
App\Controls\one::test_one('App\Controls\two', function($table) {
$table->language('PHP');
});
Then when it gets into the dynamic code, it will be referencing the right class name, and run the code as though it was this:
return $object((new App\Controls\two));

Name spaces in php

I am kind of confused about using name spaces in php,would try to explain using the problem I have with one of my project
I have a class under a namespace as
namespace BigBlueButton;
class BigBlueButton
{
public function createMeeting($createMeetingParams, $xml = '')
{
//some code
return new CreateMeetingResponse($xml);
}
}
I use it as
use \BigBlueButton;
$bbb = new BigBlueButton\BigBlueButton();
//then I try to reference the variable $bbb in a function but I can't
function easymeet_create_meeting($id) {
// $bbb = new BigBlueButton\BigBlueButton(); I don't want to create a new object here but reference the object I created above,something like this
$bbb=BigBlueButton\bbb;
}
But I can't seem to access the above variable i.e. $bbb , I tried global $bbb ,but $bbb doesn't seem to be in the global namespace, I understand I am trying to access a constant $bbb in the above code, but I showed it anyway to tell what I am trying to do
Namespacing does not extend to variables like that:
In the PHP world, namespaces are designed to solve two problems that
authors of libraries and applications encounter when creating
re-usable code elements such as classes or functions:
Name collisions between code you create, and internal PHP classes/functions/constants or third-party
classes/functions/constants.
Ability to alias (or shorten) Extra_Long_Names designed to alleviate the first problem, improving readability of source code.
http://php.net/manual/en/language.namespaces.rationale.php
Your class declaration is fine, but you've mis-used the use keyword as it should refer to specific classes, not a namespace.
use \BigBlueButton\BigBlueButton;
function easymeet_create_meeting($id, BigBlueButton $bbb) {
/* ... */
}
$bbb = new BigBlueButton();
$result = easymeet_create_meeting(1234, $bbb);
You'll notice that I've also moved $bbb into the function parameters because global state is the devil. It's also type-hinted because why not?
Better yet:
class BigBlueButton implements ButtonInterface {}
class BigRedButton implements ButtonInterface {}
And:
function easymeet_create_meeting($id, ButtonInterface $bbb) {}
$result[] = easymeet_create_meeting(1234, new BigBlueButton());
$result[] = easymeet_create_meeting(5678, new BigRedButton());
And now we're looking at the rudiments of Dependency Injection, which is a good habit to get into.
If I understand it right the problem is not with namespace.
As you said you could try using a global variable for example:
use \BigBlueButton;
global $bbb;
$bbb = new BigBlueButton\BigBlueButton();
function easymeet_create_meeting($id) {
global $bbb;
// you can use it
}
or you can send the variable to the function
function easymeet_create_meeting($id, $bbb) {
}
I do not know the entire problem, but you could also use a singleton design pattern in the class so you always get the same instance every time you use it.

How to check if class exists within a namespace?

I've got this:
use XXX\Driver\Driver;
...
var_dump(class_exists('Driver')); // false
$driver = new Driver(); // prints 123123123 since I put an echo in the constructor of this class
exit;
Well... this behaviour is quite irrational (creating objects of classes that according to PHP do not exist). Is there any way to check if a class exist under given namespace?
In order to check class you must specify it with namespace, full path:
namespace Foo;
class Bar
{
}
and
var_dump(class_exists('Bar'), class_exists('\Foo\Bar')); //false, true
-i.e. you must specify full path to class. You defined it in your namespace and not in global context.
However, if you do import the class within the namespace like you do in your sample, you can reference it via imported name and without namespace, but that does not allow you to do that within dynamic constructions and in particular, in-line strings that forms class name. For example, all following will fail:
namespace Foo;
class Bar {
public static function baz() {}
}
use Foo\Bar;
var_dump(class_exists('Bar')); //false
var_dump(method_exists('Bar', 'baz')); //false
$ref = "Bar";
$obj = new $ref(); //fatal
and so on. The issue lies within the mechanics of working for imported aliases. So when working with such constructions, you have to specify full path:
var_dump(class_exists('\Foo\Bar')); //true
var_dump(method_exists('\Foo\Bar', 'baz')); //true
$ref = 'Foo\Bar';
$obj = new $ref(); //ok
The issue (as mentioned in the class_exists() manual page user notes) is that aliases aren't taken into account whenever a class name is given as a string. This also affects other functions that take a class name, such as is_a(). Consequently, if you give the class name in a string, you must include the full namespace (e.g. '\XXX\Driver\Driver', 'XXX\\Driver\\Driver').
PHP 5.5 introduced the class constant for just this purpose:
use XXX\Driver\Driver;
...
if (class_exists(Driver::class)) {
...
}

Is there a namespace aware alternative to PHP's class_exists()?

If you try using class_exists() inside a method of a class in PHP you have to specify the full name of the class--the current namespace is not respected. For example if my class is:
<?
namespace Foo;
class Bar{
public function doesBooClassExist(){
return class_exists('Boo');
}
}
And Boo is a class (which properly autoloads) and looks like this
namespace Foo;
class Boo{
// stuff in here
}
if I try:
$bar = new Bar();
$success = $bar->doesBooClassExist();
var_dump($success);
you'll get a false... is there an alternative way to do this without having to explicitly specify the full class name ( i.e. class_exits('Foo\Boo') )?
Prior to 5.5, the best way to do this is to always use the fully qualified class name:
public function doesBooClassExist() {
return class_exists('Foo\Boo');
}
It's not difficult, and it makes it absolutely clear what you're referring to. Remember, you should be going for readability. Namespace imports are handy for writing, but make reading confusing (because you need to keep in mind the current namespace and any imports when reading code).
However, in 5.5, there's a new construct coming:
public function doesBooClassExist() {
return class_exists(Boo::class);
}
The class pseudo magic constant can be put onto any identifier and it will return the fully qualified class name that it will resolve to.......

Class as non-string argument in PHP

Example in file 1:
namespace A;
class Foo{
}
file 2:
use A\Foo;
do_stuff('A\Foo'); // <- need namespace here :(
Foo::someStaticMethod(); // <- namespace not required :D
Is there any way I can pass class names in function arguments like constants or something, so I don't need to prepend the namespace?
Update :)
When I know, that I need to pass the classnames of some classes around as string I'm used to create special class constant
namespace Foo\Bar;
class A {
const __NAMESPACE = __NAMESPACE__;
const __CLASS = __CLASS__;
}
Now you can reference the classname like
use Foo\Bar\A as Baz;
echo Baz::__CLASS;
With PHP5.5 this will be builtin
echo Baz::class;
Full-Qualified-Names (FQN) for namespaces always starts with a namespace separator
do_stuff('\A\Foo');
except (and thats the only exception) in use-statements, because there can only appear complete namespace identifiers, so for convenience you can omit it there.
However, a string is a string and where you use it as a class name is out of scope of the interpreter, so it lost the reference to the former use A\Foo-aliasing. With PHP5.5 you can write Foo::class, but I think thats not an option right now ;)
You could instantiate a new object, then call get_class() to get the fully qualified name for the class.
use A\Foo;
$foo = new Foo();
do_stuff(get_class($foo)); // get_class($foo) = '\A\Foo'
This means that the namespace of Foo is only defined by the use statement (ie. less code maintenance).
Or you can pass class reflection.
ReflectionClass
No, not without tracing the caller, as far as I know. The function you are calling must exists within the same namespace as the object you are trying to pass.
You might want to have a look at the debug_backtrace function if you require the namespace resolution. But this requires the file-paths to be translated into namespace resolutions or similar.
This is however possible: (I see Andrew has answered with the same type of solution.)
function doStuff ($obj)
{
$name = (is_object($obj))
? (new ReflectionClass(get_class($obj)))->getName()
: $obj;
// $name will now contain the fully qualified name
}
namespace Common;
class Test
{}
$testObj = new Test();
// This will work, but requires argument to be
// fully quialified or an instance of the object.
\doStuff($testObj);
\doStuff("\Common\Test");

Categories