Edit:
I solved it by getting all of the class variables using get_class_vars(), and then just acquired the correct property from that array, if it existed. Seems simple enough to me; if anyone has a different solution, I'd love to hear it (or read it, I guess..) :)
I'm trying to access a static variable in a dynamically-loaded class as follows:
$file::$disabled
(In the above statement, $file obviously references the name of a class, and $disabled is the static variable I want to access within the class.)
On PHP 5.3, this works fine; as a result of running the above code on lower versions, I get the infamous T_PAAMAYIM_NEKUDOTAYIM error.
How I've usually gotten around this error when working with older versions of PHP is to create a getter function for that variable and get that return value with call_user_func(). However, for ease of use of developers who will be adopting this code, I would like to keep $disabled as a simple variable rather than a function.
I've tried eval() on the statement, only to reach another dead end.
Does anybody know how I can make this happen?
One option would be to use reflection:
$rp = new ReflectionProperty($file, $disabled);
$value = $rp->getValue();
or
$rc = new ReflectionClass($file);
$value $rc->getStaticPropertyValue($disabled);
Related
I know you can call static methods using variable as class name like so:
$className = "Foo";
$className::Bar(); //works
But when i'm trying to use static property as variable like this:
self::$className = "Foo";
self::$className::Bar(); //doesn't
it gives me the following parse error on line where i'm trying to call the method:
Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
So how can i call that method using static property and is that even possible with syntax somewhat similar to what i described(w/o call_user_func and creating local variable that stores self::$className)?
You could do that:
$tmp = self::$className;
$tmp::Bar();
Edit
Based on your comments it seems your problem is more about OOP design than it is about syntax. Furthermore, you keep adding new restrictions every time a solution is given, which makes it difficult to provide a relevant answer.
Anyway, I'll try to summarize your options. The syntax you want does not exist (at the moment, anyway), so you have to work around it one way or another. Yes, this is annoying, and yes this means that you will have to make concessions. But that's how it is.
Here are your options so far:
Use call_user_func or forward_static_call or similar.
Use a temporary local variable. Possibly wrap that into a method if it's really bothering you (e.g. static function call($method) { $tmp = self::$classname; return $tmp::$method(); } and then use self::call('bar');)
Refactor your object design using instances instead of static methods so that you don't need to do that anymore.
Use some other terribly ugly and dangerous hack (e.g. eval(self::$classname.'::bar();'); and hope it won't come bite you in the butt.
After a refactoring, we had something like this in one of our classes:
class FooBar
{
// $foo was $bla before
private $foo;
public function setBlubbOnArrayOnlyOnce($value)
{
// $this->bla was forgotten during refactoring. Must be $this->foo
if(!isset($this->bla['blubb'])) {
$this->foo['blubb'] = $value;
}
}
}
So in the end $this->foo['blubb'] was always set, not only once.
This happens because of the magic methods of PHP. We don't want it to be possible to access fields dynamically, so I thought I just add a codesniffer rule. But I didn't found any and asked me why.
PHPStorm shows a field declared dynamically notice there, but I want this to automatically fail with codesniffer (or something similar) during our deployment cycle.
Has anybody an idea on this? Is there a good rule? Should I write my own and how? Or would it be bad practice to disable it?
Disclaimer: We use tests, but sometimes you miss things... It would be good to prevent this in the first place. Also, please don't come up with overwriting the magic methods. I don't want to have a trait/abstract whatever in every class.
This is not a codesniffer or phpstorm problem. And you cant want fix this problem with codesniffer or IDE. IDE, codesniffer, phpdocumentor, etc. -- this is "statically" analyse. And for dynamic analyse you can use e.g. phpunit.
If you want check existence of property you must use property_exists() function.
class X
{
public function __get($name)
{
$this->{$name} = null;
return $this->{$name};
}
}
$x = new X();
var_dump(property_exists($x, 'foo')); // false
var_dump($x->foo); // NULL
var_dump(property_exists($x, 'foo')); // true
Or may be you can use reflection for property http://php.net/manual/en/class.reflectionproperty.php
If you want check for "isset" you must known:
var_dump(isset($x), $x); // false + NULL with notice
$x = null;
var_dump(isset($x), $x); // false + NULL
unset($x);
var_dump(isset($x), $x); // false + NULL without notice
When you shure for this case of check you can use isset()
But you should always first check for existence of property. Otherwise you can have undefined behaviour of your code.
After a refactoring
It would be good to prevent this in the first place.
You can only catch these kind of refactoring errors by running tests after each refactoring step. This error will also bubble up, because foo['blubb'] is set to a specific value and this should cause an unwanted effect in another test - not only in the test for the setter logic.
We use tests, but sometimes you miss things...
Yes, its quite common that the coverage is not high enough.
That's why having a good test coverage is the starting point for all refactorings.
These two lines were not "green" in your coverage report:
if(!isset($this->bla['blubb'])) {
$this->foo['blubb'] = $value;
Also, please don't come up with overwriting the magic methods. I don't want to have a trait/abstract whatever in every class.
You have excluded it, but that's one way to catch the properties: by using the magic function __set() (for inaccessible vars) or property_exists() or the use of Reflection* classes to find.
Now, that its too late, you want another tool to catch the error, ok:
The tool would need to parse the PHP file and its parents (because of variable scope) and find $this->bla without a prior public|private|protected variable (class property) declaration. This will not indicate the exact type of error, just that "bla" was accessed without declaration.
Its possible to implement this as a CodeSniffer rule.
You can also give http://phpmd.org/ or https://scrutinizer-ci.com/ a try.
And, in case you are using PHP7: https://github.com/etsy/phan
tl;tr
Its complicated to determine the exact error and its context without running, evaluating and analyzing the underlying code. Just think about "dynamic variable names" and you know why: you don't even know the name of the property by looking at the source-code, because its build dynamically during program flow. A static analyzer wouldn't catch that.
A dynamical analyzer has to track all things, here $this-> accesses and would take the context into account: !isset(x). Context evaluation can find lots of common coding mistakes. In the end you can build a report: saying that $this->bla was accessed only 1 time and that indicates either that
a dynamically declared property was introduced, but never re-used, with the suggestion that you might drop it or declare it as class property
OR with added context evaluation: that and when this variable was accessed from inside a isset() - that a non-existing key of a non-declared property was accessed, without a prior set(), etc.
Now in 2017, you'are looking for tool PHPStan. I link short intro I wrote for first time users.
It does exactly what you need!
I'm trying to dig up my long-lost PHP skills and have therefore stumbled on a problem. I have a function that initializes (not sure if it's a correct word for it) a class in the following fashion:
$foo = new \Master\Slave\$bar();
$bar is a defined class, obviously. But the entire thing doesn't work. This only seems to work when I do the following:
$foo = new $bar();
But with my first example, it outputs the following error:
unexpected T_VARIABLE, expecting T_STRING
Which means that I have to manually enter the class name, correct? But, what if I'm a stubborn nerd who doesn't want to and doesn't see the efficiency in it? Hence my question; how to pull this off without getting a bloody error?
UPDATE: Got the thing working with wrapping the \Master\Slave\$bar in a $variable. Not sure if it's the correct way of doing this, but it works and props go to Visual Idiot
Variables never bound to any namespace they will be always in the global scope.
AFAIK, the namespacing does not work the way you are trying to.
This is how you should do it
namespace Master\Slave;
$bar = "theclassname";
class $bar() {
}
Docs
Is it possible to get the variable name used to reference an instantiated class from within the class? here's an example of what i mean:
class Test {
function getName(){
//some code here to get the name '$test1' in this example
}
}
$test1 = new Test
It's not a must for this to be possible, but it'd help for a project i'm working on.
You can use the variable $this to reference the object from within itself.
If you want to find the actual name of the variable $test1, it's going to be more difficult (maybe impossible, since the class has no way to know how it is being used in the global scope). But probably not worth it. Most of the time I've seen questions like that asked, people suggest that there's a design flaw and the application should depend on something other than variable names.
You could most likely do it using debug_backtrace(), however this sort of hack is extremely bad practice.
What are the performance, security, or "other" implications of using the following form to declare a new class instance in PHP
<?php
$class_name = 'SomeClassName';
$object = new $class_name;
?>
This is a contrived example, but I've seen this form used in Factories (OOP) to avoid having a big if/switch statement.
Problems that come immediately to mind are
You lose the ability to pass arguments into a constructor (LIES. Thanks Jeremy)
Smells like eval(), with all the security concerns it brings to the table (but not necessarily the performance concerns?)
What other implications are there, or what search engine terms other than "Rank PHP Hackery" can someone use to research this?
One of the issues with the resolving at run time is that you make it really hard for the opcode caches (like APC). Still, for now, doing something like you describe in your question is a valid way if you need a certain amount of indirection when instanciating stuff.
As long as you don't do something like
$classname = 'SomeClassName';
for ($x = 0; $x < 100000; $x++){
$object = new $classname;
}
you are probably fine :-)
(my point being: Dynamically looking up a class here and then doesn't hurt. If you do it often, it will).
Also, be sure that $classname can never be set from the outside - you'd want to have some control over what exact class you will be instantiating.
It looks you can still pass arguments to the constructor, here's my test code:
<?php
class Test {
function __construct($x) {
echo $x;
}
}
$class = 'Test';
$object = new $class('test'); // echoes "test"
?>
That is what you meant, right?
So the only other problem you mentioned and that I can think of is the security of it, but it shouldn't be too difficult to make it secure, and it's obviously a lot more secure than using eval().
I would add that you can also instanciate it with a dynamic number of parameters using :
<?php
$class = "Test";
$args = array('a', 'b');
$ref = new ReflectionClass($class);
$instance = $ref->newInstanceArgs($args);
?>
But of course you add some more overhead by doing this.
About the security issue I don't think it matters much, at least it's nothing compared to eval(). In the worst case the wrong class gets instanciated, of course this is a potential security breach but much harder to exploit, and it's easy to filter using an array of allowed classes, if you really need user input to define the class name.
There may indeed be a performance hit for having to resolve the name of the variable before looking up the class definition. But, without declaring classes dynamically you have no real way to do "dyanmic" or "meta" programming. You would not be able to write code generation programs or anything like a domain-specific language construct.
We use this convention all over the place in some of the core classes of our internal framework to make the URL to controller mappings work. I have also seen it in many commercial open source applications (I'll try and dig for an example and post it). Anyway, the point of my answer is that it seems well worth what is probably a slight performance decrease if it makes more flexible, dynamic code.
The other trade-off that I should mention, though, is that performance aside, it does make the code slightly less obvious and readable unless you are very careful with your variable names. Most code is written once, and re-read and modified many times, so readability is important.
Alan, there's nothing wrong with dynamic class initialisation. This technique is present also in Java language, where one can convert string to class using Class.forClass('classname') method. It is also quite handy to defer algorithm complexity to several classes instead of having list of if-conditions. Dynamic class names are especially well suited in situations where you want your code to remain open for extension without the need for modifications.
I myself often use different classes in conjunction with database tables. In one column I keep class name that will be used to handle the record. This gives me great power of adding new types of records and handle them in unique way without changing a single byte in existing code.
You shouldn't be concerned about the performance. It has almost no overhead and objects themselves are super fast in PHP. If you need to spawn thousands of identical objects, use Flyweight design pattern to reduce memory footprint. Especially, you should not sacrifice your time as a developer just to save milliseconds on server. Also op-code optimisers work seamlessly with this technique. Scripts compiled with Zend Optimizer did not misbehave.
So I've recently encountered this, and wanted to give my thoughts on the "other" implications of using dynamic instantiation.
For one thing func_get_args() throws a bit of a wrench into things. For example I want to create a method that acts as a constructor for a specific class (e.g. a factory method). I'd need to be able to pass along the params passed to my factory method to the constructor of the class I'm instantiating.
If you do:
public function myFactoryMethod()
{
$class = 'SomeClass'; // e.g. you'd get this from a switch statement
$obj = new $class( func_get_args() );
return $obj;
}
and then call:
$factory->myFactoryMethod('foo','bar');
You're actually passing an array as the first/only param, which is the same as new SomeClass( array( 'foo', 'bar' ) ) This is obviously not what we want.
The solution (as noted by #Seldaek) requires us to convert the array into params of a constructor:
public function myFactoryMethod()
{
$class = 'SomeClass'; // e.g. you'd get this from a switch statement
$ref = new ReflectionClass( $class );
$obj = $ref->newInstanceArgs( func_get_args() );
return $obj;
}
Note: This could not be accomplished using call_user_func_array, because you can't use this approach to instantiate new objects.
HTH!
I use dynamic instantiation in my custom framework. My application controller needs to instantiate a sub-controller based on the request, and it would be simply ridiculous to use a gigantic, ever-changing switch statement to manage the loading of those controllers. As a result, I can add controller after controller to my application without having to modify the app controller to call them. As long as my URIs adhere to the conventions of my framework, the app controller can use them without having to know anything until runtime.
I'm using this framework in a production shopping cart application right now, and the performance is quite favorable, too. That being said, I'm only using the dynamic class selection in one or two spots in the whole app. I wonder in what circumstances you would need to use it frequently, and whether or not those situations are ones that are suffering from a programmer's desire to over-abstract the application (I've been guilty of this before).
One problem is that you can't address static members like that, for instance
<?php
$className = 'ClassName';
$className::someStaticMethod(); //doesn't work
?>
#coldFlame: IIRC you can use call_user_func(array($className, 'someStaticMethod') and call_user_func_array() to pass params
class Test {
function testExt() {
print 'hello from testExt :P';
}
function test2Ext()
{
print 'hi from test2Ext :)';
}
}
$class = 'Test';
$method_1 = "testExt";
$method_2 = "test2Ext";
$object = new $class(); // echoes "test"
$object->{$method_2}(); // will print 'hi from test2Ext :)'
$object->{$method_1}(); // will print 'hello from testExt :P';
this trick works in both php4 and php5 :D enjoy..