I have the following which works just fine:
use frontend\models\modules\shipping\Usps;
$shipping = new Usps;
If however I use the following it throws the error: Class 'Usps' not found
use frontend\models\modules\shippingl\Usps;
I am loading classes dynamically, thus I cannot use the namespace when calling new $class, eg. I might also have:
use frontend\models\modules\payment\Usps;
$class = 'Usps';
$shipping = new $class;
How can I make this work using a variable? If I don't use namespaces/autoload and just include the class file it works just fine.
This question is not a duplicate, the referenced question/answers do not declare a 'use'.
This is the only way I see this working without security issues and tons of errors, especially if $class coming from user input.
I added a "Ups" class so it makes a little more sense.
use frontend\models\modules\payment\Usps;
use frontend\models\modules\payment\Ups;
if($class == 'Usps') {
$shipping = new Usps;
} else if($class == 'Ups') {
$shipping = new Ups;
} else {
// No valid class found - handle accordingly
}
Related
I"m sorry now but I don't know if I can get all the details possible to make an educated guess on what exactly is going on.
I'm trying to check to see if a class exists, if it doesn't I need to create the class. Some classes have sub classes (namespaces).
namespace {
class wattsjus {
}
}
namespace wattsjus {
class OtherClass {
}
}
so whether I __autoload wattsjus or wattsjus\OtherClass I'm loading both classes.
I can tell that up until the point I load the classes they are not there if I try and autoload the 'inner class'. After I load the class I get an error that the class is already loaded. Yet I can print text just before that point and that line is never reached.
I am using class_exists to make sure the wattsjus class is not loaded. Before I load the class this is true (loaded). yet if I try to make an instance of wattsjus it cannot be found.
I'm using the following code to check where the class was created and it's stating a line number that is never reached at this point. I have an print statement before this line to be sure.
$reflector = new ReflectionClass('wattsjus');
echo $reflector->getFileName();
A great mystery to me as to how code could have been reached without executing the previous line.
Edit, Warning this is ugly code:
function __autoload($class_name) {
echo 'in';
$parts = explode('\\',$class_name);
if($class_name == $_GET['u5u'] || $parts[0] == $_GET['u5u']) {
$cl = class_exists($_GET['u5u'], false);
echo $cl ? 'yes' : 'no';
if(!$cl) {
if($_GET['e5e'] != 'Prod') { $env = $_GET['e5e']; }
$q = "SELECT f.`Name`, f.`$env"."ParameterList` `ParameterList` FROM `Function` f WHERE f.UserId = ".SiteUserId." AND `Status` = 'A' ORDER BY INSTR(f.`Name`, '\\\\') > 0, f.`Name`";
$results = DataObject::GetDBObject()->Query($q, 'wattsjus_json');
$class = "namespace { class $_GET[u5u] {";
$class .= GetFunctions($results);
$class .= '}}';
echo 'I\'m not here yet';
eval($class);
} else {
$reflector = new ReflectionClass('wattsjus');
echo $reflector->getFileName();
}
here the output that is produced trying to get OtherClass is:
inyes/home4/wattsjus/public_html/ajson/global_base.php(25) : eval()'d code<br />
<b>Fatal error</b>: Class 'wattsjus\OtherClass' not found in <b>/home4/wattsjus/public_html/ajson/global_base.php(112) : runtime-created function</b> on line <b>1</b><br />
so to me this is saying that the class wattsjus already exists which should include OtherClass in the namespace. Though it cannot be found. If I let it keep going and create wattsjus again it will say the class has already been defined. Very confusing I know.
I changed the structure of the classes to use "namespace wattsjus\OtherClass" rather than make a OtherClass class and for some reason PHP is less confused by this. Really weird glitch/bug I'd have to say.
namespace {
class wattsjus {
}
}
namespace wattsjus\OtherClass {
}
I found a problem that I not sure if is a bug of the php or on my code (probably mine) so let me show you what is happening:
<?php namespace MyApp\Conciliation;
use SimpleExcel\SimpleExcel;
use ForceUTF8\Encoding;
use MyApp\Conciliation\Gol;
class Conciliation {
protected function equalizeFile($file, $providerName)
{
$type = false;
$nfile = 'public'.$file;
// TEST 1: the ideal aproach. not working (see error#1 bellow)
$provider = new $providerName();
// TEST 2: working, getting the correct response
$provider = new Gol();
// TEST 3: working, getting the correct response
$provider = new MyApp\Conciliation\Gol();
$provider->equalize($nfile);
}
Note, the $providerName = 'Gol';
error1
Class 'Gol' not found
http://inft.ly/N8Q6F4B
So, there is any way that I could keeping using variables to instantiate aliases similar as above?
Edit, Problem solved: working example
<?php namespace MyApp\Conciliation;
use SimpleExcel\SimpleExcel;
use ForceUTF8\Encoding;
class Conciliation {
protected function equalizeFile($file, $providerName)
{
$type = false;
$nfile = 'public'.$file;
$providerName = "MyApp\\Conciliation\\".$providerName;
$provider = new $providerName();
$provider->equalize($nfile);
}
http://php.net/manual/en/language.namespaces.dynamic.php
If you are calling the class dynamically, you have to use the full path to the class.
So, your call to equalizeFile should be something like:
equalizeFile("myFile", "MyApp\\Conciliation\\Gol");
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();
What would be a good way (along with any pros and cons) of initializing an instance of a PHP class with another object of the same class (ideally in PHP 4.x)?
Here in initialize() is essentially what I'd like to be able to do (example is extremely simplified from my use-case, see below):
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product->initialize($product2);
echo $product->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name) {
$this->name = $name;
}
function initialize($product) {
// I know this cannot be done this way in PHP.
// What are the alternatives and their pros & cons?
$this = $product;
}
}
I know this may not be "good programming practice"; with 20+ years programming experience on other languages I know a bit about what's good and what's not. So hopefully we won't get hung up on if doing this makes sense or not. I have a use-case working with some open-source code that I can't change so please just bear with me on my need for it. I'm actually trying to create an OOP wrapper around some really ugly array code buried deep in the core of WordPress.
I'm trying to write it so in future versions they can move away from the ugly array-based code because everyone will be using the new API that otherwise fully encapsulated these nasty arrays. But to make it work elegantly I need to be able to do the above (in PHP 4.x) and I don't want to write code that just copies the properties.
Thanks in advance for your help.
UPDATE
Many of you are suggesting clone but unless I misunderstand that doesn't address the question. clone makes a copy; that's not the crux of the question. I'm instead trying to get the constructed object to "become" the object passed in. At this point I'm assuming there isn't a way to do that based on the fact that 0 out of 5 answers have suggested anything but I'll wait a bit longer before selecting a best in case it was simply that my questions was unclear.
In PHP 5, object cloning might be more relevant:
http://php.net/manual/en/language.oop5.cloning.php
You can define a special __clone method.
In PHP 4 and 5, you can copy properties via:
function copy($obj)
{
foreach (get_object_vars($obj) as $key => $val)
{
$this->$key = $val;
}
}
However, you wrote "I don't want to write code that just copies the properties," and I'm not exactly sure what you mean by that.
Preferred way of doing this is to use clone keyword and to implement appropriate __clone() method if needed as mentioned by other posters. Another trick way of doing this (con: slow, pros: can be stored, sent over network and works identical in php4/5) is to serialize an object and then unserialize to create new copies of it with identical variable values.
Example:
$productCopy = unserialize(serialize($product));
EDIT: Sorry, misunderstood what you were asking for. You will have to initialize variables of the object being constructed with passed in object's variables inside of the constructor. You can't return a reference to another object from the constructor.
Example:
public function __construct($name, $object = null) {
if($object) {
foreach(get_object_vars($object) as $k => $v) {
$this->$k = $v;
}
} else {
$this->name = $name;
}
}
class Product {
var $name;
function __construct($value) {
if (is_a($value, 'Product')) {
$this->name = $value->name;
} else {
$this->name = $value;
}
}
}
Similarly, you can use instanceof instead of is_a if you prefer (depending on your PHP version).
Now you can pass a Product instance OR a name to the construct.
$product = new Product('Something');
$clone = new Product($product);
This is the best way of doing it so far:
http://www.blrf.net/howto/51_PHP__How_to_control_object_instances_in_PHP_.html
Don't use "new", instead use a static function that returns the instance you want.
I have done the following:
class MyClass {
private static $_instances;
public static function get($id) {
if (!self::$_instances) self::$_instances = Array();
$class = get_called_class();
if (!array_key_exists($class, self::$_instances)) self::$_instances[$class] = Array();
if (!is_numeric($id) || $id == '') throw new Exception('Cannot instantiate a non-numeric ID.');
if (array_key_exists($id, self::$_instances[$class])) return self::$_instances[$class][$id];
else {
self::$_instances[$class][$id] = new static($id);
return self::$_instances[$class][$id];
}
}
function __construct($id=false) {
// new instance code...
// I use $id=false to create new a db table row not load an old one
}
}
Usage:
// New instance
$a = new MyClass();
$a = new MyClass;
// MyClass with $id = 1
$b = MyClass::get(1);
$c = MyClass::get(1);
$d = new MyClass(1);
$b and $c point to the same object, while $d is a new one.
Caveats:
Garbage collection will no longer apply as your instances are stored in a static array
You'll have to change your code to use MyClass::get
Notes in my code:
New instances are called with "new static" instead of "new self" to use late static bindings.
You can set your constructor to private. This will break all your old code if you use "new", but will ensure you don't get double instances or more. You'll have to change a bit in the get function's arguments and code to allow $id=false or $id=-1 or whatever.
maybe
$product = new Product('Widget');
$product2 = new Product(null, $product);
echo $product2->name; // echos "Widget #2"
class Product {
var $name;
function __constructor($name, $product = null) {
$this->name = !empty($name) ? $name : $product->name;
}
}
Adding another answer due to it being radically different.
$product = new Product('Widget');
$product2 = new Product('Widget #2');
$product =& $product2;
echo $product->name; // echos "Widget #2"
That should work.
I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.