I can not create a class instance from a string in Symfony - php

What I wanted to is to create class instance from string in Symfony framework to use Doctrine Repository. Here is my code block:
$type = "SomeClassName";
$productType = $this->productTypeProvider->getProductType($product->getBarcode());
$repository = $this->doctrine->getRepository($type::class);
I want to use SomeClassName::class above but it gives me the following error. Any help would be appeciated:
Cannot use "::class" on value of type string (500 Internal Server Error)

As mentioned above, using the full class name worked perfectly.

To understand why this doesn't work, it's useful to have some background on how namespaces are implemented in PHP:
Every class, interface, trait, and enum has a "fully-qualified class name" (FQCN), which can contain zero or more namespace separators (\)
A particular file, or code-block, can have a "current namespace" defined.
Within that block, you can have any number of use statements, which provide short-hands within the scope of that file.
The expansion of the current namespace, and the currently in-scope use statements, happens at compile-time - in case you're not aware, PHP is a compiled language, the compiler is just invoked automatically, for a single file.
The ::class syntax is part of this compile-time expansion: it substitutes any relevant use statements or current namespace, and then treats the result as a string. In other words, this:
namespace Foo\Bar;
use Something\Else;
use Something\Else\Again as Alias;
echo Example::class;
echo Else::class;
echo Else\More::class;
echo Alias::class;
Is compiled exactly as though it said this:
echo 'Foo\Bar\Example';
echo 'Something\Else';
echo 'Something\Else\More';
echo 'Something\Else\Again';
Those strings can then be passed around to wherever needs an FQCN.
The key point here is that this all happens before any code in the file is executed. When you tried to write $type::class, the value of $type is something that will only be known at run-time; far too late for the namespace expansion in the compiler to affect it. Theoretically, the compiler could know what you meant by "SomeClassName"::class, but it would be unnecessary extra complexity since you can just write SomeClassName::class instead.
If you are building a string at run-time, you are responsible for turning it into an FQCN.
Note that despite its name, ::class doesn't actually check that the result is a class name, it just does string manipulation based on the current namespace and use statements, so the following are all equivalent:
namespace Example;
use App\Entity; // [1]
use App\Entity\SomeClassName; // [2]
// explicit FQCN
$type = 'App\Entity\SomeClassName';
// straight-forward expansion of [2]
$type = SomeClassName::class;
// expansion of [1]
$type = Entity\SomeClassName::class;
// expand the namespace via [1] then add class name
$namespace = Entity::class;
$type = $namespace . '\SomeClassName';
// expand to a non-existent class, then add more text
$prefix = Entity\So::class;
$type = $prefix . 'meClassName';

Related

PHP:: Fully-qualified method literal to string

Essentially, I seek to pass a static class method to a callback, but do not wish to do so using a hard-coded string, but rather the fully-qualified class method literal. We can do that using classes like so:
$name = NS\FooClass::class;
instead of:
$name = 'NS\FooClass';
which will give us the string of the fully-qualified name of the class. I seek to be able to do something similar for a class method like so:
$name = NS\FooClass::foo_method::method;
instead of:
$name = 'NS\FooClass::foo_method';
It is more manageable and I can use the IDE functionality way better using the literals. Any similar way I can achieve what I want with the class methods without using strings?
There is currently no such mechanism built into the language. It has been suggested - see for instance this discussion from Feb 2020 - but there are more nuances to think about than might be immediately apparent; notably:
Should the syntax resolve at run-time and check the existence of the class and the method (::class in most cases doesn't; a bare function like strlen::func would have to because of the way namespaces resolve; an object implementing __callStatic could never be used this way)?
Should the result be a string, an array (see below), or a Closure object?
Anyway, that's a topic for elsewhere...
As the manual page on the callable type says, there are two ways to specify a static method for use as a callback:
As a string, as in your example 'NS\FooClass::foo_method'
As an array where the first part is a class name, and the second part is a the method name: ['NS\FooClass', 'foo_method']
Since only the class name needs to be qualified with namespace information, you can use ::class with the second syntax to get nearly what you wanted:
$callback = [NS\FooClass::class, 'foo_method'];
This allows any decent IDE to spot the reference to the class, and allows you to reference it by an imported or aliased name.
It's worth noting that if the callable type is specified in a parameter or return type declaration or a docblock, some IDEs (e.g. PhpStorm) will "understand" either format as a reference to the method, and include it in features like "find usages" and "go to declaration".

PHP property decleration accepts array and do not accept any of other expressions that could be evaluated at the compilation process

while studying oop in PHP, I noticed that the property declaration accepts an array as value as mentioned here PHP documentation
class Test{
public $var7 = array(true, false);
}
and I noticed that the documentation says :
This declaration may include an initialization, but this initialization must be a constant value--that is, it must be able to be evaluated at compile time and must not depend on run-time information in order to be evaluated.
and after reading this article to know how the compilation process works, I realized that within the compilation process, some expressions would be evaluated and optimized if it possible like the below snippet :
var x = strlen ("testing") => int(7);
if using the array as a value in declaring property worked because it's evaluated in the compilation process, then why did not the below initialization work if logically both of them could be evaluated at the compilation process and this is the condition to initialize a property in a class?
Class Test {
public $vars=strlen("random"); // Fails
}
The short answer to your question is that strlen() is a function while array() is a keyword.
The critical difference to understand is that keywords always reference the same thing regardless of context.
From php.net:
These words have special meaning in PHP. Some of them represent things which look like functions, some look like constants, and so on - but they're not, really: they are language constructs. You cannot use any of the following words as constants, class names, function or method names.
Functions, on the other hand, could be defined differently depending on where you are calling them.
Consider this simplistic example:
First a file we'll call "functions.php".
//functions.php
namespace My_Project_Namespace;
function strlen($string){
return 10; //In my project, all strings are length 10! 10 is a nice round number...
}
In this file, I am overriding the built-in strlen() function with another one. This is possible because my function is defined inside a namespace (in this case, My_Project_Namespace).
Now consider your file, but this time let's put it in our namespace (you should be name-spacing all your functions and classes)
//Test.php
namespace My_Project_Namespace;
Class Test {
public $vars=strlen("random"); // Fails
}
strlen() has 2 definitions depending on the namespace. Since knowing the current namespace depends on runtime information the compiler cannot know which to use for initialization in the class. Even if you didn't define a custom strlen() function you still couldn't do this because knowing that there isn't another version strlen() also depends on runtime information!
array() is a totally different beast. It is a keyword, you cannot define another meaning for array() so the compiler doesn't have to worry about one existing.

ClassName::class vs 'ClassName' in PHP

In one of my projects we are having a function which validates objects like:
if( true == valObj( $someObj, 'SomeClass' ) ) {
//Do some work;
}
While using this I thought this can be written like:
if( true == valObj( $someObj, SomeClass::class ) ) {
//Do some work;
}
Now I just want to know which approach is better and why?
Or are they same?
The ::class notation is generally better as it allows for easier usage finding and thereby refactoring: If you use an IDE like PHPStorm you can easily rename a class, and it will find and update all usages of OldClassName::class to NewClassname::class. This is not the case for the hardcoded string variant.
If you use namespaces using this notation can also result in less inline characters.
If you are running a PHP version that supports this notation, use it :)
SomeClass::class returns the full namespace including the class name.
Whilst if you use the string notation you have to add the namepace (if any) yourself.
Edit:
It doesn't matter which notation you choose, although I personally prefer the string notation as it shows you directly what namespace the class is in while scrolling through code.
from php.net
The special ::class constant are available as of PHP 5.5.0, and allows for fully qualified class name resolution at compile, this is useful for namespaced classes.
<?php
namespace foo {
class bar {
}
echo bar::class; // foo\bar
}
?>
This is also a magic constant. have a look at http://php.net/manual/en/language.constants.predefined.php

PHP equivalent of enum in namespace [C/C++]

I'd like to organize my constants neatly.
In C++ I would've put enums or variables in a namespace (or more) like so:
namespace foo
{
namespace bar
{
enum herp { derp, sherp, sheep };
}
namespace Family
{
const string mom = "Anita";
const string son = "Bob";
const string daughter = "Alice";
}
}
This would give me the possibility to access them like
int x = foo::bar::derp;
string nameOfMom = foo::Family::mom;
string nameOfSon = foo::Family::son;
How can I implement this in PHP?
As far as the enum is concerned: There is no enum in PHP. You can write an array (which is in fact a HashTable underneath) or you'll have to use a (separate) namespace and/or define a class for that, given that an enum is closer to a class, I'd probably opt for the latter and go ahead and write:
class FakeEnum
{
const DERP = 0;
const SHERP = 1;
const SHEEP = 2;
}
jycr753 already linked this question, and it does show how you could emulate enums in PHP, but if you ask me, that's just taking it that little bit too far. Using a ReflectionClass just to emulate a missing construct is like modifying your car so it can double as a motorcycle.
On to the namespaces:
Namespaces in PHP are relatively "new", and they are not 100% equivalent to C++ namespaces. For one, they can't be nested in the way you are trying. For that, you'll have to resort to declaring classes inside a given namespace, and accept that only allows for 1 additional level.
All things asside, I take it you're looking for something like this:
namespace Foo
{
const FOOBAR = 123;
}
namespace Bar
{
const FOOBAR = 456;
}
namespace Work
{
const FOOBAR = __NAMESPACE__;
include 'global_const.php';
echo 'Global FOOBAR const is: ', \FOOBAR, PHP_EOL,
'Foo::FOOBAR is: ', \Foo\FOOBAR, PHP_EOL,
'Bar::FOOBAR is: ', \Bar\FOOBAR, PHP_EOL,
'Work::FOOBAR is: ', FOOBAR, PHP_EOL;
}
Where the global_const.php file defines a global constant like so:
define('FOOBAR', 'global');//just to be consistent ;-P
The resulting output is:
Global FOOBAR const is: global
Foo::FOOBAR is: 123
Bar::FOOBAR is: 456
Work::FOOBAR is: Work
Of course, in reality, your code will be spread-out over multiple files, and more often than not, you'll only use a single namespace for that file, and use other namespaces (≃ using in C++):
namespace My\Core\Components\Output;
use Foo,
Bar,
My\Core\Components\Input as CoreInput;
use External\Component\Input as HelperInput;
The inconsistencies in the PHP namespacing system are well documented (google search them). But to give you an example, if I start my file with the statements above, the following statement:
$myVar = Foo\SOME_CONSTANT;
resolves to
global namespace (\ for short)
->Foo namespace
-> SOME_CONSTANT
But if I were to remove the use Foo, the same statement resolves to:
\
-> My
-> Core
-> Components
-> Output
-> Foo
-> SOME_CONSTANT
Now that may seem perfectly reasonable, but the same rule does not apply to the core functions: \str_replace or str_replace are both resolved correctly, the only difference is that the latter will first perform a lookup for a function called str_replace in the current namespace, to then fall-back to the global namespace.
Ok, with some "goodwill" you could argue that this, too, is fairly predictable behaviour. Sadly, strangely or maliciously, PHP does not behave in the same way when using its core objects (like DateTime, stdClass, Exception or PDO...).
Take the mysqli_* extension, for example: you can use its procedural API throughout all of your namespaces and be a happy camper, but if you prefer the OO API, you'll have to use the use statements, or add a backslash whenever you write new \mysqli().
Ok, so PHP has its faults, but of course, we all know that much, as for your question, I believe I you have an answer now, and for me to continue this rant would be utterly pointless. Everything you need to know about namespaces can be found in the manual, BTW

Importing classes and namespaces in PHP: What difference does a leading backslash make?

What's the difference between those two:
use Exception;
use \Exception;
Or those:
use Foo\Bar;
use \Foo\Bar;
The manual says:
Note that for namespaced names (fully
qualified namespace names containing
namespace separator, such as Foo\Bar
as opposed to global names that do
not, such as FooBar), the leading
backslash is unnecessary and not
allowed, as import names must be fully
qualified, and are not processed
relative to the current namespace.
But I don't really understand this, as all of the above variants work, i.e. it definitely is not "not allowed".
A look into zend_do_use showed, that is_global (set, when there is a leading backslash) is only used for a warning in the following case:
namespace {
use Exception;
}
Which tells me: "The use statement with non-compound name 'Exception' has no effect". (Though doing the same with use \Exception would have just as little effect, but does not throw a warning.)
So: Am I missing something? Is there actually some difference?
The manual specifies the backslash as unnecessary, which naturally means that if you still use it that the meaning is equivalent. However, as you have pointed out, the manual says that it is supposedly not allowed, which is false.
However, there is something else troubling with the manual. They advertise this:
// importing a global class
use \ArrayObject;
If it is true that import names are not processed relative to the current namespace, then use \ArrayObject and use ArrayObject must have the same meaning. What else could use ArrayObject refer to other than the global one? In practice, the engine will import the global one.
Also, with bugs such as this:
http://bugs.php.net/bug.php?id=49143
I believe there is confusion over what the standard is supposed to be.
To answer your question: there is no difference. However, if I was the engine developer who was also a believer of the no-leading-slash standard, then I wouldn't need to consider a case where someone wrote use \Exception;. I believe this was likely the case.
In fact there is no difference in using leading backslash in importing namespaces at the moment and also information in PHP manual has changed:
Note that for namespaced names (fully qualified namespace names
containing namespace separator, such as Foo\Bar as opposed to global
names that do not, such as FooBar), the leading backslash is
unnecessary and not recommended, as import names must be fully
qualified, and are not processed relative to the current namespace.
So now there is true information that using leading backslash is not recommended but there is no info that it's not allowed at it was in past.
So at the moment:
use Exception;
use \Exception;
those 2 lines work the same but you should rather use the first one.
Usually the leading backslash defines, that the identifier is absolute. If its missing, the interpreter assumes, that it is a relative identifier.
This is an absolute identifier:
$x = new \Name\Space\To\Class();
This is a relative identifier, because of the no leading slash. It's relative to the current namespace:
namespace Name\Space;
$x = new To\Class;
This is also a relative identifier. In this case, its resolved against the use statement, because the last part (alias) is the same, as the first of the class:
namespace Other\Name\Space;
use Name\Space;
$x = new Space\To\Class;
However, because in namespace and use statements only absolute identifiers (fully qualified names) are allowed, it's ok to omit it here. In namespace, it's even not allowed to set the leading backslash.
For further information on how PHP resolves the different namespace declarations, see the namespace rules manual.
The leading backslash means the global namespace. If you are in a namespace's scope, you have to use that to reach the global namespace. For example:
namespace A
{
class A
{
public function __construct()
{
echo('namespace: A<br />');
}
}
}
namespace B\A
{
class A
{
public function __construct()
{
echo('namespace: B\\A<br />');
}
}
}
namespace B
{
class B
{
public function __construct()
{
new \A\A(); // namespace: A
new A\A(); // namespace: B\A
}
}
new B();
}
With leading backslash you got the absolute path, and without it you got the relative path.
le't say we have
namespace MyNamespace
use Exception;
use \Exception;
then the first use actually imports class MyNamespace\Exception and the second one just the main class \Exception
so you can have something like
namespace MyNamespace;
class Exception extends \Exception{ }
and then I can
throw new \Exception('Exception from global namespace');
throw new \MyNamespace\Exception('Exception from MyNamespace');

Categories