Creating PHP class instance with a string into Symfony2 - php

I need to instance different object into a same method.
I have found soluce here:
Creating PHP class instance with a string
But when I use that on Controller of Symfony2 I have this error :
Attempted to load class "PhotoType" from the global namespace.
Did you forget a "use" statement?
I didn't understand because I have add all of my "use"
namespace DIVE\FileUploaderBundle\Controller;
use DIVE\FileUploaderBundle\Entity\Photo;
use DIVE\FileUploaderBundle\Form\PhotoType;
...
class DefaultController extends Controller {
public function listFileAction($fileType) {
$em = $this->getDoctrine()->getManager();
$repository = $em->getRepository("FDMFileUploaderBundle:".$fileType);
$files = $repository->findAll();
$forms = array();
foreach ($files as $file) {
$class = $fileType."Type";
array_push($forms, $this->get('form.factory')->create(new $class(), $file));
}
$formViews = array();
foreach ($forms as $form) {
array_push($formViews, $form->createView());
}
return $this->render("FDMFileUploaderBundle:Default:list".$fileType.".html.twig", array(
"forms" => $formViews
)
);
}
}
Sorry for my English, I am learning it.

Try this:
foreach ($files as $file) {
$class = 'DIVE\FileUploaderBundle\Form\' . $fileType . 'Type';
// ...
}
Actually you could find the answer in the last comment of the accepted answer to the very question you linked to:
Please note the when using namespaces, you must supply the full path: $className = '\Foo\Bar\MyClass'; $instance = new $className(); – Giel Berkers Dec 16 '14 at 8:23
Basically, in order to instantiate a class from a string, you must use the fully qualified name of the class - including the namespace. Check the page Namespaces and dynamic language features from PHP Manual for a quick explanation and examples.

According to http://php.net/manual/en/language.namespaces.dynamic.php
One must use the fully qualified name (class name with namespace prefix). Note that because there is no difference between a qualified and a fully qualified Name inside a dynamic class name, function name, or constant name, the leading backslash is not necessary.

Related

php: class not found (dynamically created instance) [duplicate]

So I created these two classes
//Quarter.php
namespace Resources;
class Quarter {
...
}
//Epoch.php
namespace Resources;
class Epoch {
public static function initFromType($value, $type) {
$class = "Quarter";
return new $class($value, $type);
}
}
Now this is a a very simplified version of both, but is enough to illustrate my question. The classes as they are shown here will not work as it will not find the Quarter class. To make it work I could change the $class variable to
$class = "\Resources\Quarter";
So my question is: Why do I need to use the namespace here when both classes are already members of the same namespace. The namespace is only needed when I put the classname in a variable so doing:
public static function initFromType($value, $type) {
return new Quarter($value, $type);
}
will work without problems. Why is this and is there any potential traps here I need to avoid?
Because strings can be passed around from one namespace to another. That makes name resolution ambiguous at best and easily introduces weird problems.
namespace Foo;
$class = 'Baz';
namespace Bar;
new $class; // what class will be instantiated?
A literal in a certain namespace does not have this problem:
namespace Foo;
new Baz; // can't be moved, it's unequivocally \Foo\Baz
Therefore, all "string class names" are always absolute and need to be written as FQN:
$class = 'Foo\Baz';
(Note: no leading \.)
You can use this as shorthand, sort of equivalent to a self-referential self in classes:
$class = __NAMESPACE__ . '\Baz';

Creating objects dynamically in PHP [duplicate]

So I created these two classes
//Quarter.php
namespace Resources;
class Quarter {
...
}
//Epoch.php
namespace Resources;
class Epoch {
public static function initFromType($value, $type) {
$class = "Quarter";
return new $class($value, $type);
}
}
Now this is a a very simplified version of both, but is enough to illustrate my question. The classes as they are shown here will not work as it will not find the Quarter class. To make it work I could change the $class variable to
$class = "\Resources\Quarter";
So my question is: Why do I need to use the namespace here when both classes are already members of the same namespace. The namespace is only needed when I put the classname in a variable so doing:
public static function initFromType($value, $type) {
return new Quarter($value, $type);
}
will work without problems. Why is this and is there any potential traps here I need to avoid?
Because strings can be passed around from one namespace to another. That makes name resolution ambiguous at best and easily introduces weird problems.
namespace Foo;
$class = 'Baz';
namespace Bar;
new $class; // what class will be instantiated?
A literal in a certain namespace does not have this problem:
namespace Foo;
new Baz; // can't be moved, it's unequivocally \Foo\Baz
Therefore, all "string class names" are always absolute and need to be written as FQN:
$class = 'Foo\Baz';
(Note: no leading \.)
You can use this as shorthand, sort of equivalent to a self-referential self in classes:
$class = __NAMESPACE__ . '\Baz';

How can I name a fully-qualified type using a variable?

If I have a class name in $name, how can I create an object of type \folder\$name? Ideally I'd like to interpolate the $name so that I can create the object with just a single line of code.
The following doesn't seem to work:
$obj = new \folder\$name();
The problem is youre trying to use a variable as part of a FQCN. You cannot do that. The FQCN can be a variable itself like:
$fqcn = '\folder\classname';
$obj = new $fqcn();
Or you can delcare the namespace at the top of the file:
namespace folder;
$fqcn = 'classname';
$obj = new $fqcn;
Or if the file belongs to another namespace you can use the class to "localize" it:
namespace mynamespace;
use folder\classname;
$fqcn = 'classname';
$obj = new $fqcn();
A more concrete example of something i assume to be similar to what you are trying to do:
namespace App\WebCrawler;
// any local uses of the File class actually refer to
// \App\StorageFile instead of \App\WebCrawler\File
use App\Storage\File;
// if we didnt use the "as" keyword here we would have a conflict
// because the final component of our storage and cache have the same name
use App\Cache\File as FileCache;
class Client {
// the FQCN of this class is \App\WebCrawler\Client
protected $httpClient;
protected $storage;
protected $cache
static protected $clients = array(
'symfony' => '\Symfony\Component\HttpKernel\Client',
'zend' => '\Zend_Http_Client',
);
public function __construct($client = 'symfony') {
if (!isset(self::$clients[$client])) {
throw new Exception("Client \"$client\" is not registered.");
}
// this would be the FQCN referenced by the array element
$this->httpClient = new self::$clients[$client]();
// because of the use statement up top this FQCN would be
// \App\Storage\File
$this->storage = new File();
// because of the use statement this FQCN would be
// \App\Cache\File
$this->cache = new FileCache();
}
public static function registerHttpClient($name, $fqcn) {
self::$clients[$name] = $fqcn;
}
}
You can read in more detail here: http://php.net/manual/en/language.namespaces.dynamic.php
Shouldn't it be
new \folder\$arr[0];
and not
new \folder\$arr[0]();
Also, I'm not too familiar with PHP, and I've never see this syntax before. What I suggest is:
namespace \folder;
$obj = new $arr[0];
I'm not sure if you can do it with just one line and without namespaces.

namespace instantiation through variable

namespace user;
use robot\r;
$namespace = 'r\someClass';
$class = new $namespace(); // does not work
$namespace = '\robot\r\someClass';
$class = new $namespace(); // does work
Why doesn't this work as expected?
The reason I am using a variable is b/c "someClass" isn't known ahead of time.
So the code looks like this:
if ( $class == 'someClass' )
{
$namespace = 'r\someClass';
}
elseif ( $class == 'someOtherClass' )
{
$namespace = 'r\someOtherClass';
}
$class = new $namespace();
This is easy to work around, but I don't understand why:
$class = new r\someClass() will work
and $class = new $namespace() will not work.
Updated:
When you use dynamic class name, you have to include the namespace name.
So the below will work:
namespace user;
use robot\r; // use is not necessary when you use dynamic class name.
$namespace = 'robot\r\someClass'; // for a dynamic class name, namespace is required.
$class = new $namespace();
Note the leading slash is not necessary, because there is no difference between a qualified and a fully qualified Name inside a dynamic class name, function name, or constant name.
Check the document here.
Should be able to use #xdazz answer, but you can also alias the namespace. The reason it is failing is you have to full quality namespace path.
use robot\r as r;
$classname = 'r\someClass';
without the
as r
part you have to fully qualify the path later.
I stumbled upon the same issue a few minutes ago - should've looked in here ealier :)
unfortunately i can't comment yet so here's a tiny hint that you can also use the __NAMESPACE__ constant instead of retyping the whole qualified namespace when your class is relative to the current namespace...
In my case i have a small factory method:
public function getService($name)
{
$className = __NAMESPACE__ . '\Service\\' . $name;
return new $className();
}

PHP: how to autoload interfaces and abstracts

I have this autoloader class to autoload classes initially, but now I want to autoload interfaces and abstracts as well.
So I made the change following this answer,
$reflection = new ReflectionClass($class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
I tested it but this autoloader class does not load interfaces at all. Any ideas what I have missed?
For instance, this is my interface file,
interface_methods.php
and its content,
interface methods
{
public function delete();
}
Below is my entire this autoloader class.
class autoloader
{
/**
* Set the property.
*/
public $directory;
public $recursive;
public function __construct($directory, $recursive = array('search' => 'models') )
{
# Store the data into the property.
$this->directory = $directory;
$this->recursive = $recursive;
# When using spl_autoload_register() with class methods, it might seem that it can use only public methods, though it can use private/protected methods as well, if registered from inside the class:
spl_autoload_register(array($this,'get_class'));
}
private function get_class($class_name)
{
# List all the class directories in the array.
if ($this->recursive)
{
$array_directories = self::get_recursive_directory($this->directory);
}
else
{
if (is_array($this->directory)) $array_directories = $this->directory;
else $array_directories = array($this->directory);
}
# Determine the class is an interface.
$reflection = new ReflectionClass($class_name);
$file_pieces = explode('\\', $class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
# Loop the array.
foreach($array_directories as $path_directory)
{
if(file_exists($path_directory.$file_name))
{
include $path_directory.$file_name;
}
}
}
public function get_recursive_directory($directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator($directory),
RecursiveIteratorIterator::CHILD_FIRST
);
# This will hold the result.
$result = array();
# Loop the directory contents.
foreach ($iterator as $path)
{
# If object is a directory and matches the search term ('models')...
if ($path->isDir() && $path->getBasename() === $this->recursive['search'])
{
# Add it to the result array.
# Must replace the slash in the class - dunno why!
$result[] = str_replace('\\', '/', $path).'/';
//$result[] = (string) $path . '/';
}
}
# Return the result in an array.
return $result;
}
}
PHP makes no difference between any class or interface or abstract class. The autoloader function you define always gets the name of the thing to autoload, and no kind of hint which one it was.
So your naming strategy cannot be autoloaded because you prefix interfaces with "interface_" and classes with "class_". Personally I find such a naming convention rather annoying.
On the other hand, your autoloader is completely unperformant. It scans whole directory trees recursively just to find one class! And the next class has to do all the work again, without the benefit of having done it before!
Please do implement a PSR-0 autoloader if you really want to do it on your own (and not use things like composer to do it for you) and stick to this naming scheme for classes and interfaces.
And please select a distinguishing classname prefix or namespace, and as a first step check inside your autoloader if the class to be loaded has this prefix. Return instantly if it has not. This frees you from having to spin the harddrive and see if the filename for the class exists.
If the prefix does not match, it is not "your" class that wants to be loaded, so your autoloader cannot know how to do it and shouldn't even try, but a different autoloader that was registered will know.

Categories