PHP : dynamic instance with not fully-qualified namespaces - php

I'd like to instanciate my class doing :
use somedir\http as Http;
$S_bodyWriterType = 'Http\\' . strtolower($S_requestBodyPayloadType) . '\\RequestBodyWriter';
$this->_O_requestBodyWriter = new $S_bodyWriterType;
It says the class does not exist. However THAT would work (no string involved here) :
$this->_O_requestBodyWriter = new Http\xml\RequestBodyWriter;
And that would also work of course (the namespace is fully qualified) :
$S_bodyWriterType = 'somedir\http\\' . strtolower($S_requestBodyPayloadType) . '\\' . 'RequestBodyWriter';
$this->_O_requestBodyWriter = new $S_bodyWriterType;
i'd definitely prefer to use shortened namespaces instead of having to write long, fully-qualified namespaces in different places of the codebase and having to change them all in case the directory location moves. I've been pulling my hair off for a while now over this.
Thanks for help !

OK, you provided the Bug report yourself ;) But thats the fact: If you define a classname in a string, its not said, that the object is created in the same context.
namespace y {
use a\b as B;
$GLOBALS['class'] = 'B\\MyClass';
}
namespace z {
use k\l as B;
$classname = $GLOBALS['class'];
$a = new $classname;
}
Thus you need to define classnames in string full qualifed. I suggest to use (namespace-/class-)constants
use a\b as B;
const NAMESPACE_B = '\\a\\b';
$classname = NAMESPACE_B . '\\MyClass';
If the class you want to instanciate is in a subnamespace, remember, that the pseudo-constant __NAMESPACE__ always exists;
namespace a;
use a\b as B;
$classname = __NAMESPACE__ . '\\b\\MyClass';
Additional in your case I suggest to create a factory
use somedir\http as Http;
class RequestBodyWriterFactory {
public function create($type) {
$classname = __NAMESPACE__ . "\\$type\\RequestBodyWriter";
return new $classname;
}
}
// somewere else
$this->_O_requestBodyWriter = $this->factory->create(strtolower($S_requestBodyPayloadType));
This way you have more control on what is created and how its created.

Related

Instantiating a PHP class using a variable is not working

This is working:
$x = new classname();
This is not working:
$class = "classname";
$x = new $class();
The error I get is "Class classname not found". PHP version is 5.4.22. Any ideas? As far as I have researched into this topic this is exactly what you need to do in order to instantiate a class using a variable.
My actual testcode (copy+paste), $build = 1:
//include the update file
$class="db_update_" . str_pad($build, 4, '0', STR_PAD_LEFT);
require_once(__ROOT__ . "/dbupdates/" . $class . ".php");
$x = new db_update_0001();
$xyz="db_update_0001";
$x = new $xyz();
The class definition:
namespace dbupdates;
require_once("db_update.php");
class db_update_0001 extends db_update
{
...
}
I just found out that my editor added
use dbupdates\db_update_0001;
to the file. So that explains why "new db_update_0001();" is working. What i want to achieve is that I dynamically include database updates which are stored in files like dbupdates/db_update_0001.php
Regards,
Alex
You have to use the full qualified class name. Which is namespace\classname. So in your case the code should be:
$x = new db_update_0001();
$xyz="dbupdates\db_update_0001";
$x = new $xyz();
The use statement is useless if you like to instantiate a class by using a variable as classname.
Try this
<?php
$className = yourClassName();
$x = new $className;
?>

PHP Dynamic Autoloader via extends

Currently, I have a pretty average php auto loader loading in my classes. I've come to a point in development where I will need to override a class with another class based on a variable. I'm running a custom SaaS application, and we have the occasional organization that will demand some weird change to the way the system functions. In the past, we've filled up our code with garbage by massive IF statements for orgs, such as
if(ORGID == 'ABCD'){
//do this insane thing
}else{
//Normal code here.
}
So, I've been toying with the idea of a dynamic auto loader. ORGID is one of the very first defines in the application. The entire application is running under a fixed namespace of COMPANY\PRODUCT; Here's a code sample of what I was thinking I could do.
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
Since ROOT and ORGID are defined before the autoloader comes into play, I thought about doing this
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php')){
include(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php');
}elseif(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
While this works, I have to copy/paste my entire class into the org specific class file, then make changes. I can't extend the primary class, because the classes share the same name. The only option I've been able to come up with that would allow me to extend my classes in such a way is to never load the base class.
Instead of
$myObj = new myClass();
I call
$myObj = new customMyClass();
and I have a file called customMyClass(); which simply extends myClass without making any changes to it. This way, the auto loader will load customMyClass and then load myClass. If an organization has their own customMyClass in their organization folder, then it will load in that class, which will then properly load in myClass.
While this works, we have hundreds of class files, which would double if we had a custom file for each.
I've seen a couple of examples that use eval to handle similar situations. Is that really the only way to do this type of thing?
UPDATE:
Just so I'm clear, the end goal is so that the thousands of places we've called $myObj = new myClass(); doesn't need to be rewritten.

Using namespace in if / else statements

I am manipulating the same file to manage two external api classes.
One api class is based on namespaces, the other one is not.
What I would like to do is something like this:
if($api == 'foo'){
require_once('foo.php');
}
if($api == 'bar'){
require_once('bar.php');
use xxxx\TheClass;
}
The problem is that when I do so, the following error message is returned:
Parse error: syntax error, unexpected T_USE in etc...
Question 1: Do I have to use two different files to manage the two classes or is it possible to manage both while using namespaces in the document? From what I see, it does not seem to be.
Question 2: Why namespaces could not be used inside if() statements?
Thank you for your help
Please see Scoping rules for importing
The use keyword must be declared in the outermost scope of a file (the global scope) or inside namespace declarations. This is because the importing is done at compile time and not runtime, so it cannot be block scoped.
All use does is import a symbol name into the current namespace. I would just omit the import and use the fully qualified class name, eg
switch ($api) {
case 'foo' :
require_once('foo.php');
$someVar = new SomeClass();
break;
case 'bar' :
require_once('bar.php');
$someVar = new \xxxx\TheClass();
break;
default :
throw new UnexpectedValueException($api);
}
You can also simply add the use statement to the top of your script. Adding it does not commit you to including any files and it does not require the symbol to be known, eg
use xxxx\TheClass;
switch ($api) {
case 'foo' :
require_once('foo.php');
$someVar = new SomeClass();
break;
case 'bar' :
require_once('bar.php');
$someVar = new TheClass(); // imported above
break;
default :
throw new UnexpectedValueException($api);
}
Use statements should be placed before any executable code (you can have namespaces, classes, functions and constants definitions). Actually it can, just have to be placed unconditionally in some namespace, so no ifs or inside functions. Also don't be afraid of putting use at the top, it does not load any class or instantiate object. it acts only as alias that is used when encountered later during execution.
As for having them in one file, it is possible to have many namespaces and even global namespace in one file:
<?php
namespace
{
class myclass{}
}
namespace mynamespace
{
class myclass{}
}
But I strongly discourage such "management" of code. Each class should have it's own file.
Alright, just confirming what i said was true.. Try something like this :
*Test_1.php*
$API = "test_1";
if ($API === "test"){
}elseif ($API === "test_1"){
require ("test.php");
$API = new daryls\testt;
}
$API->test();
test.php
namespace daryls;
class testt {
public function test(){
echo "Started";
}
}
Running this has worked without a hitch
Another option for API class versioning is to set the classname as a variable conditionally.
// Get class name
switch ($api_version) {
case 'v0':
// No class namespace
$class = 'Handler';
break;
case 'v1':
// Namespaced class
$class = 'API\App\v1\Handler';
break;
case 'v2':
// Namespaced class
$class = 'API\App\v2\Handler';
break;
default:
throw new Exception('Unknown API version: ' . $api_version);
}
// Get class object
$handler = new $class();

Is there a way to dynamically change the name of the class being autoloaded?

For instance, say I have the following code.
$foo = new bar();
And an autoloader like this.
function autoload($class_name) {
$class_file_name = str_replace('_', '/', $class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
}
}
But the class I really want to load is in the folder 'foo/bar.php', and the real class name is actually foo_bar. Is there a way to dynamically change the name of the class being autoloaded? For instance, something like this?
function autoload(&$class_name) {
$class_name = 'foo_' . $class_name;
$class_file_name = str_replace('_', '/', $class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
}
}
I know if something like this is possible, it is not exactly best practice, but I would still like to know if it is.
No. You could load a different file. You could load no file. You could load several files. But after autoloading, PHP expects the class to exist like it was called.
If you call a class X, you can't magically give PHP class Y.
Maybe it's enough to set up the filesystem like that, but still keep literal class names?
PS
I've wanted this for a while too. When I didn't have access to namespaces yet. Now that I do, all my problems are solved =) If you do have access to namespaces, you should 'study' PSR-0.
Maybe you can use class_alias, this way:
PHP requires class X
You want load class Y instead.
You create an alias X for class Y, so X will behave like Y.
E.g.:
function autoload($class_name) {
$real_class_name = 'foo_' . $class_name;
$class_file_name = str_replace('_', '/', $real_class_name) . '.php';
if (file_exists($class_file_name)) {
include($class_file_name);
class_alias($real_class_name, $class_name);
}
}
Hope it can help you.

PHP custom class loader

i made a custom class loader function in php
something like..
load_class($className,$parameters,$instantiate);
its supposed to include the class and optionally instantiate the class specified
the problem is about the parameters. ive been trying to pass the parameters all day
i tried
load_class('className',"'param1','param2'",TRUE);
and
load_class('className',array('param1','param2'),TRUE);
luckily nothing works xD
is it possible to pass the params?
i even tried..
$clas = new MyClass(array('param1','param2'));
here it is..
function load_class($class, $param=null, $instantiate=FALSE){
$object = array();
$object['is_required'] = require_once(CLASSES.$class.'.php');
if($instantiate AND $object['is_required']){
$object[$class] = new $class($param);
}
return $object;
}
if you are in PHP 5.x I really really recommend you to use autoload. Prior to PHP 5.3 you should create sort of "namespace" (I usually do this with _ (underscore))
autoload allows you to include classes on the fly and if your classes are well designed the overhead is minimun.
usually my autoload function looks like:
<?php
function __autoload($className) {
$base = dirname(__FILE__);
$path = explode('_', $className);
$class = strtolower(implode('/',$path));
$file = $base . "/" . $class;
if (file_exists($file)) {
require $file;
}
else {
error_log('Class "' . $className . '" could not be autoloaded');
throw new Exception('Class "' . $className . '" could not be autoloaded from: '.$file);
}
}
this way calling
$car = new App_Model_Car(array('color' => 'red', 'brand' => 'ford'));
the function will include the class
app/model/car.php
Seems to me that you should be using __autoload() to just load classes as they are referenced and circumvent having to call this method manually. This is exactly what __autoload() is for.

Categories