Class not found when calling another class name in namespace - php

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));

Related

PHPUnit_Framework_Assert::assertClassHasStaticAttribute() must be a class name

I'm just starting out learning PHPUnit. I have a seemingly very simple test;
namespace stats\Test;
use stats\Fetch;
class FetchTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->fetch = new Fetch;
}
public function testStoresListOfAssets()
{
$this->assertClassHasStaticAttribute('paths', 'Fetch'); //line 17
}
}
My Fetch class is;
namespace stats;
class Fetch
{
public static $paths = array(
'jquery' => 'http://code.jquery.com/jquery.js'
);
}
The error I get when running PHPUnit;
PHPUnit_Framework_Exception: Argument #2 (string#Fetch)of PHPUnit_Framework_Assert::assertClassHasStaticAttribute() must be a class name
It's probably something very silly but I can't understand the problem
The PHPUnit_Framework_Assert use the PHP method class_exists to check if the classname you have indicated is correct (check this link to see the full code):
if (!is_string($className) || !class_exists($className, FALSE)) {
throw PHPUnit_Util_InvalidArgumentHelper::factory(2, 'class name');
}
The problem you have here is the method class_exists doesn't take into account this command:
use stats\Fetch;
So that, you have to indicate the full path to make it work. In this link of stackoverflow you can find more information about that problem. You should change your assert to something like this:
$this->assertClassHasStaticAttribute('paths', '\\stats\\Fetch');
You're not supplying the fully qualified class name and in the context of assertClassHasStaticAttribute() or any other method/function outside of the scope of your (test) class the use statement that complements the class name.
If you're using PHP 5.5 or later (which you should ;) use Fetch::class.
In general you should prefer ::class over strings for class names as modern IDEs can help you with refactoring when changing class names which is close to impossible if you use strings.
To sum it up, for your example it would be:
public function testStoresListOfAssets()
{
$this->assertClassHasStaticAttribute('paths', Fetch::class);
}

Include file into a namespace

I'm working with Magento, but this isn't a Magento specific question.
Let's say that you're working with foo.php with contains the class Foo. In Magento, /local/foo.php will be included if it exists, otherwise /core/foo.php will be included. In both, the class Foo is defined. The problem here is that both files contain the class Foo, therefore the class in /local/foo.php can't extend the class in /core/foo.php. Ultimately this requires all of the code from /core/foo.php to be copied in /local/foo.php minus my customizations.
/core/foo.php - I can't change this file!
<?php
class Foo {
public function test() {
echo 'core/foo.php :: Foo :: test';
}
}
?>
/local/foo_include.php
<?php
namespace mage {
require '../core/foo.php;
}
?>
/local/foo.php - I can't put a namespace in this file, as I have no control over the file instantiating this class.
<?php
require './foo_include.php';
use mage;
class Foo extends mage\Foo {
function __construct() {
var_dump('Un-namespaced Foo!');
}
}
$foo = new Foo();
$foo->test();
?>
The above doesn't work, saying that mage\Foo doesn't exist. (It does work if the core Foo class is defined inside foo_include instead of being brought in through an include.
Is there any way around this that I'm missing?
Normally when I see something like this, I feel like the approach isn't right and the problem needs to be examined at a higher view. That said, if this is a hack and you know it, this tweak to your hack will work. But again, I don't recommend either approach.
Change foo_include.php to this:
eval('namespace Mage {?>'.file_get_contents(__DIR__ . '/../core/foo.php').'}');
I'm unsure how Magento works, but with Symfony, the correct solution would be to override the dependency's class via parameters.
http://symfony.com/doc/current/cookbook/bundles/override.html
Is there something similar in Magento? Then you can override the class with your class, and extend the other. This still means they'd need to be in different namespaces, however the framework won't try to go to \Foo since you overrode it. Like such:
# original class for a dependency
dependency.class = \Foo
# overriden with new class
dependency.class = My\Foo
Then in your version of Foo:
class My\Foo extends \Foo {}

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");

Relative nested namespaces in PHP

I'll try to describe the situation I'm having problems with:
I have one main folder. I keep all files in there (empty classes for a reason), and one sub-folder, containing the same files, with all implementations here (empty classes extend them).
the main folder's namespace is declared as Project/Folder, and the sub-folder as Project/Folder/Subfolder. These are class's declarations:
namespace Project\Folder;
class Foo extends Subfolder\Foo { }
namespace Project\Folder\Subfolder;
class Foo { }
What I want to achieve is to be able to call other classes from inside of the Project\Folder\Subfolder\Foo through these empty classes on the lower level, with only its name, e.g.:
namespace Project\Folder\Subfolder;
class Foo {
function bar() {
Another_Class::do_something();
}
}
By default, there will be called Another_Class from the Project\Folder\Subfolder namespace. I want this to refer to Another_Class from the Project\Folder namespace with the same syntax - is that possible?
I hope I explained this clear enough, if not, write a commend, and I'll try to make it clearer.
You can achieve that using the use statement.
use Project\Folder\Subfolder\Another_Class as SomeAlias;
// ...
SomeAlias::doSomething();
// or
$object = new SomeAlias();
$object->doSomething();
Alternatively, you would have to reference the entire namespace:
\Project\Folder\Subfolder\Another_Class::doSomething();
// or
$object = new \Project\Folder\Subfolder\Another_Class();
$object->doSomething();
More information here.

Categories