Динамическое создание классов в php
PHP’s implementation of namespaces is influenced by its dynamic nature as a programming language. Thus, to convert code like the following example into namespaced code:
Example #1 Dynamically accessing elements
class classname
function __construct ()
echo __METHOD__ , «\n» ;
>
>
function funcname ()
echo __FUNCTION__ , «\n» ;
>
const constname = «global» ;
?php
$a = ‘classname’ ;
$obj = new $a ; // prints classname::__construct
$b = ‘funcname’ ;
$b (); // prints funcname
echo constant ( ‘constname’ ), «\n» ; // prints global
?>
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.
Example #2 Dynamically accessing namespaced elements
namespace namespacename ;
class classname
function __construct ()
echo __METHOD__ , «\n» ;
>
>
function funcname ()
echo __FUNCTION__ , «\n» ;
>
const constname = «namespaced» ;
?php
/* note that if using double quotes, «\\namespacename\\classname» must be used */
$a = ‘\namespacename\classname’ ;
$obj = new $a ; // prints namespacename\classname::__construct
$a = ‘namespacename\classname’ ;
$obj = new $a ; // also prints namespacename\classname::__construct
$b = ‘namespacename\funcname’ ;
$b (); // prints namespacename\funcname
$b = ‘\namespacename\funcname’ ;
$b (); // also prints namespacename\funcname
echo constant ( ‘\namespacename\constname’ ), «\n» ; // prints namespaced
echo constant ( ‘namespacename\constname’ ), «\n» ; // also prints namespaced
?>
User Contributed Notes 8 notes
When extending a class from another namespace that should instantiate a class from within the current namespace, you need to pass on the namespace.
namespace foo ;
class A public function factory () return new C ;
>
>
class C public function tell () echo «foo» ;
>
>
?>
namespace bar ;
class B extends \ foo \ A <>
class C public function tell () echo «bar» ;
>
>
?>
include «File1.php» ;
include «File2.php» ;
$b = new bar \ B ;
$c = $b -> factory ();
$c -> tell (); // «foo» but you want «bar»
?>
You need to do it like this:
When extending a class from another namespace that should instantiate a class from within the current namespace, you need to pass on the namespace.
namespace foo ;
class A protected $namespace = __NAMESPACE__ ;
public function factory () $c = $this -> namespace . ‘\C’ ;
return new $c ;
>
>
class C public function tell () echo «foo» ;
>
>
?>
namespace bar ;
class B extends \ foo \ A protected $namespace = __NAMESPACE__ ;
>
class C public function tell () echo «bar» ;
>
>
?>
include «File1.php» ;
include «File2.php» ;
$b = new bar \ B ;
$c = $b -> factory ();
$c -> tell (); // «bar»
?>
(it seems that the namespace-backslashes are stripped from the source code in the preview, maybe it works in the main view. If not: fooA was written as \foo\A and barB as bar\B)
Important to know is that you need to use the *fully qualified name* in a dynamic class name. Here is an example that emphasizes the difference between a dynamic class name and a normal class name.
namespace namespacename \ foo ;
class classname
<
function __construct ()
<
echo ‘bar’ ;
>
>
$a = ‘\namespacename\foo\classname’ ; // Works, is fully qualified name
$b = ‘namespacename\foo\classname’ ; // Works, is treated as it was with a prefixed «\»
$c = ‘foo\classname’ ; // Will not work, it should be the fully qualified name
// Use dynamic class name
new $a ; // bar
new $b ; // bar
new $c ; // [500]: / — Uncaught Error: Class ‘foo\classname’ not found in
// Use normal class name
new \ namespacename \ foo \ classname ; // bar
new namespacename \ foo \ classname ; // [500]: / — Uncaught Error: Class ‘namespacename\foo\namespacename\foo\classname’ not found
new foo \ classname ; // [500]: / — Uncaught Error: Class ‘namespacename\foo\foo\classname’ not found
Be careful when using dynamic accessing namespaced elements. If you use double-quote backslashes will be parsed as escape character.
$a = «\namespacename\classname» ; //Invalid use and Fatal error.
$a = «\\namespacename\\classname» ; //Valid use.
$a = ‘\namespacename\classname’ ; //Valid use.
?>
Please be aware of FQCN (Full Qualified Class Name) point.
Many people will have troubles with this:
function factory ( $class ) <
return new $class ;
>
// File2.php
$bar = \ foo \ factory ( ‘Bar’ ); // Will try to instantiate \Bar, not \foo\Bar
?>
To fix that, and also incorporate a 2 step namespace resolution, you can check for \ as first char of $class, and if not present, build manually the FQCN:
function factory ( $class ) <
if ( $class [ 0 ] != ‘\\’ ) <
echo ‘->’ ;
$class = ‘\\’ . __NAMESPACE__ . ‘\\’ . $class ;
>
// File2.php
$bar = \ foo \ factory ( ‘Bar’ ); // Will correctly instantiate \foo\Bar
$bar2 = \ foo \ factory ( ‘\anotherfoo\Bar’ ); // Wil correctly instantiate \anotherfoo\Bar
Case you are trying call a static method that’s the way to go:
class myClass
public static function myMethod ()
return «You did it!\n» ;
>
>
$foo = «myClass» ;
$bar = «myMethod» ;
echo $foo :: $bar (); // prints «You did it!»;
?>
It might make it more clear if said this way:
One must note that when using a dynamic class name, function name or constant name, the «current namespace», as in http://www.php.net/manual/en/language.namespaces.basics.php is global namespace.
One situation that dynamic class names are used is in ‘factory’ pattern. Thus, add the desired namespace of your target class before the variable name.
namespaced.php
// namespaced.php
namespace Mypackage ;
class Foo public function factory ( $name , $global = FALSE )
if ( $global )
$class = $name ;
else
$class = ‘Mypackage\\’ . $name ;
return new $class ;
>
>
class A function __construct ()
echo __METHOD__ . «
\n» ;
>
>
class B function __construct ()
echo __METHOD__ . «
\n» ;
>
>
?>
global.php
// global.php
class A function __construct ()
echo __METHOD__ ;
>
>
?>
index.php
// index.php
namespace Mypackage ;
include( ‘namespaced.php’ );
include( ‘global.php’ );
$a = $foo -> factory ( ‘A’ ); // Mypackage\A::__construct
$b = $foo -> factory ( ‘B’ ); // Mypackage\B::__construct
$a2 = $foo -> factory ( ‘A’ , TRUE ); // A::__construct
$b2 = $foo -> factory ( ‘B’ , TRUE ); // Will produce : Fatal error: Class ‘B’ not found in . namespaced.php on line .
?>
as noted by guilhermeblanco at php dot net,
public function create ( $class ) return new $class ();
>
>
$foofact = new fact ();
$bar = $foofact -> create ( ‘bar’ ); // attempts to create \bar
// even though foofact and
// bar reside in \foo
//single or double quotes with single or double backslash in dynamic namespace class.
namespace Country_Name class Mexico function __construct ()echo __METHOD__ , «
» ;
>
>
$a = ‘Country_Name\Mexico’ ; //Country_Name\Mexico::__construct
$a = «Country_Name\Mexico» ;
//Country_Name\Mexico::__construct
$a = ‘\Country_Name\Mexico’ ;
//Country_Name\Mexico::__construct
$a = «\Country_Name\Mexico» ;
//Country_Name\Mexico::__construct
$a = «\\Country_Name\\Mexico» ;
//Country_Name\Mexico::__construct
$o = new $a ;
/* if your namespace name or class name start with lowercase n then you should be alart about the use of single or double quotes with backslash */
namespace name_of_country class Japan function __construct ()
echo __METHOD__ , «
» ;
>
$a = ‘name_of_country\Japan’ ;
//name_of_country\Japan::__construct
$a = «name_of_country\Japan» ;
//name_of_country\Japan::__construct
$a = ‘\name_of_country\Japan’ ;
//name_of_country\Japan::__construct
//$a = «\name_of_country\Japan»;
//Fatal error: Uncaught Error: Class ‘ ame_of_country\Japan’ not found
//In this statement «\name_of_country\Japan» means -first letter n with «\ == new line(«\n). for fix it we can use double back slash or single quotes with single backslash.
$a = «\\name_of_country\\Japan» ;
//name_of_country\Japan::__construct
$o = new $a ;
>
namespace Country_Name class name function __construct ()echo __METHOD__ , «
» ;
>
>
$a = ‘Country_Name\name’ ;
//Country_Name\Norway::__construct
$a = «Country_Name\name» ;
//Country_Name\Norway::__construct
$a = ‘\Country_Name\name’ ;
//Country_Name\Norway::__construct
//$a = «\Country_Name\name»;
//Fatal error: Uncaught Error: Class ‘\Country_Name ame’ not found
//In this statement «\Country_Name\name» at class name’s first letter n with «\ == new line(«\n). for fix it we can use double back slash or single quotes with single backslash
$a = «\\Country_Name\\name» ;
//Country_Name\name::__construct
$o = new $a ;
//»\n == new line are case insensitive so «\N could not affected
- Namespaces
- Namespaces overview
- Defining namespaces
- Declaring sub-namespaces
- Defining multiple namespaces in the same file
- Using namespaces: Basics
- Namespaces and dynamic language features
- namespace keyword and __NAMESPACE__ constant
- Using namespaces: Aliasing/Importing
- Global space
- Using namespaces: fallback to global function/constant
- Name resolution rules
- FAQ: things you need to know about namespaces
Dynamic Class Instantiation and NS in PHP
Join the DZone community and get the full member experience.
The first step in developing every program’s business, right after designing and defining the required classes, is class instantiation. And it is a very straightforward process. You call the class constructor which is exactly the same as the class name, followed by the respective namespace, pass the required arguments (if any), and that’s it.
The scenario described above is exactly what happens in nearly all situations. But there is a condition where exceptions may arise: whenever you try to instantiate a class dynamically.
So, what is dynamic class instantiation/loading?
Dynamic Class Instantiation allows the loading of code that is not known about before a program starts. This way, the program doesn’t need to be aware of the class name before it’s loaded and run.
class BicycleModel < //@ToDo: implement BicycleModel >class CarModel < //@ToDo: implement CarModel >// Normal Class Loading $myTodayChoice = new BicycleModel(); // Dynamic Class Loading $vehicleName = 'Bicycle'; // You should first concatenate $vehicleName & Model, then use resulting variable for creating class instance. // Keep in mind that you're not allowed to merge class instantiation & concatenation. // $myTodayChoice - new $vehicleName.'Model'(); IS NOT VALID $className = $vehicleName . 'Model'; $myTodayChoice = new $className();
As you see, we first specify the desired class name in the flow of the program and then use it to declare the class.
So far, what we’ve discussed is occurring in a global scope. Now its time to dig into Dynamic Class Loading in Namespaced PHP.
It seems there is nothing new to worry about: we’ll just declare some namespaces, then every package and class has to be defined in its proper namespace. So we just have to follow the rules about normal class loading. Let’s do it and see what happens:
/* File Component/Package/Vehicle/BicycleModel.php */ namespace Component\Package\Vehicle; class BicycleModel < //@ToDo: implement BicycleModel >------------------------------------------------------ /* File Component/Package/Vehicle/CarModel.php */ namespace Component\Package\Vehicle; class CarModel < //@ToDo: implement CarModel >------------------------------------------------------ /* File Component/Package/Vehicle/VehicleFactory.php */ namespace Component\Package\Vehicle; class VehicleFactory < private $vehicleName; public function __construct($vehicle = 'Bicycle')< $this->$vehicleName = $vehicle; > public function getInstance()< $className = $this->$vehicleName . 'Model'; return new $className(); //This will return \BicycleModel & NOT \Component\Package\Vehicle\BicycleModel /* This means it won't work as expected. We thought it will return a BicycleModel in the same namespace * that is defined, But it didn't happen. Continue reading to see why */ > >
Defining namespaces and importing packages from other namespaces is performed at compile-time and so does not affect dynamic class names (which are determined at run-time).
So, in order to fix bug, we have to rewrite the getInstance() method, prefixing te $className with the proper namespace:
public function getInstance()< $className = 'Component\\Package\\Vehicle\\' . $this->$vehicleName . 'Model'; return new $className(); //This will return \BicycleModel & NOT \Component\Package\Vehicle\BicycleModel /* This means it won't work as expected. We thought it will return a BicycleModel in the same namespace * that is defined, But it didn't happen. Continue reading to see why */ >
Published at DZone with permission of Behrad Khodayar . See the original article here.
Opinions expressed by DZone contributors are their own.