Alright so I think this may be extremely basic, but it has me stumped nonetheless. Before I get to my question, let me demonstrate the concept my question is based on with this working example:
<?php
$a = 'Stack';
$b = $a.' Overflow';
echo $b; // Result: "Stack Overflow"
?>
In the above example, $b is defined as the combination of $a and ' Overflow'.
Now, let's assume I want to do the same thing as above except I don't want to use global variables. I want to use classes. This is how I have attempted to achieve that:
<?php
class ClassName {
public $a = 'Stack';
public $b = $this->a.' Overflow'; // This gives me: "Parse error: syntax error, unexpected '$this'"
}
$instantiate = new ClassName;
echo $instantiate->$b; // Desired result: "Stack Overflow"
?>
As stated, this attempt results in an error. To me, this attempt seems logical, but I guess PHP doesn't think so.
Question: Is this possible, and if so, how do I go about achieving the desired result? Also, if you could explain why my attempt has failed (logically), that would be a bonus.
I've searched and researched for hours on end trying to find an answer or figure this out on my own, but for the life of me, I cannot find anyone or anything that even touches on this (including other Stack Overflow threads). I can't even find anywhere saying it's impossible or anything of the sort either.
I'm a PHP novice, so I may need more explanation than others, but any kind of help or general guidance would be much appreciated. Thank you.
You cannot use $this when defining the class because it refers to a concrete object context which becomes available after instantiation. You can use a constructor for that kinds of stuff.
class ClassName
{
public $a = 'Stack';
public $b = "";
function __construct()
{
$this->b = $this->a.' Overflow';
}
}
$instantiate = new ClassName;
echo $instantiate->b;
Quoting from the PHP Docs about defining class properties
They are defined by using one of the keywords public, protected, or private, followed by a normal variable declaration. 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.
(my emphasis)
Concatenation cannot be evaluated at compile time, only at run time.
If you need to do this, then define the initial value for the property in the constructor
This would throw a syntax error because you are using $this in a scope that it is not allowed to:
The pseudo-variable $this is available inside any class method when that method is called from within an object context. $this is a reference to the calling object (usually the object to which the method belongs, but possibly another object, if the method is called statically from the context of a secondary object).
This means, even if you wanted you won't be able to do what you want to do because of such restriction. This is a common restriction that is in many programming languages, properties have to be initialized to static values. To solve your issue you can do any of the following:
In your constructor, create those variables.
Create the second variable on its own, and create a method that concatenates the two.
Create a magic method to do it for you.
The same restriction holds true for static class properties.
Related
In PHP it is possible to get a full class name via class name resolution like this:
Example:
namespace Name\Space;
class ClassName {}
echo ClassName::class;
Output: Name\Space\ClassName
This is better than using the string Name\Space\ClassName directly in the code because code introspection especially in IDEs can find an error directly.
I wonder if there is something similar for methods of a class - this would be specifically useful for callback functions.
This is how you can basically can pass a callback:
$a = function($callback,$arg) { return $callback($arg); }
$a('getInfo',5);
Instead of passing a string here (which might change), I would prefer to do something like this:
$a(MyClass::class::getInfo,5);
With I "go to declaration" click in the IDE I could go directly to getInfo plus I see errors in case with method does not exist anymore. Is there a way to achieve what I want to do here?
In fact, you work with callable type. And PHP allows setting method/function name only as a string. But if you use classes and objects you will have a different way to set callback. For example:
$a = function($callback, $arg) {
return call_user_func($callback, $arg));
}
// call a static method of the class with
// using fullname space and method name as a string
$a('Name\Space\MyClass::getInfo',5);
// call a static method of the class
// with using ::class
$a([MyClass::class, 'getInfo'], 5);
// call a method of an object
$myObject = new MyClass();
$a([$myOject, 'getInfo'], 5);
Three possibilities.
(1)
echo `__CLASS__`;
...returns namespace\classname as a string.
(2)
If you're trying to get the namespace\classname from another class, i.e., not the one where you're currently executing code, then I would suggest setting a public property inside each class such as:
public static $classname = __CLASS__;
which you could then access from anywhere as:
ClassName::$classname
Put it in each of your classes. Always use the same property name.
(3)
Have you considered the PHP function debug_backtrace() which returns a call stack with the most recent call at index = 0;
So, if:
$caller = debug_backtrace();
Then, $caller[0]['class'] contains the fully qualified class name, including any namespace, at the point where you called debug_backtrace().
I'm guessing that #2 is the solution that will work for you.
Just thought of a 4th possibility that doesn't depend on you adding any code to each class. Might add some overhead though, but so does my 3rd solution above.
(4)
$declared_classes = get_declared_classes();
This lists all of the classes currently declared within the PHP scope as fully qualified namespace\classname. You could search the returned array for partial string matches within the array and return the whole namespace\classname string.
One thing to keep in mind. You might have duplicates if different namespaces have same-named classes.
I've added this as a comment somewhere else but figured it might warrant an actual answer to this question. If you use:
$callback = [MyClass::class, 'myMethod'];
Then at least one IDE (PhpStorm) will recognize this as the callable that it is, allow you to navigate to it, mention it in "show usages" and automatically change it when it is renamed through a refactor. I use this in my code if, for instance, I reference a method in a test:
$this->mock(MyClass::class, function(MockInterface $mock) {
$mock->shouldReceive([MyClass:class, 'myMethod'][1])->andReturn(10);
});
Not the cleanest syntax, but it's workable.
While I understand that static methods can be invoked through a variety of approaches, such as:
A::staticFunction();
OR
$class = 'A';
$class::staticFunction();
OR
$a = new A(); // Assume class has been defined elsewhere
$a->staticFunction();
However, could someone please explain why the following won't work, and if possible, how to make this work (without resorting to the solution provided):
// Assume object $b has been defined & instantiated elsewhere
$b->funcName::staticFunction(); // Where funcName contains the string 'A'
This produces the following PHP parse error:
Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
Typical working solution (follows second approach) (preferably avoid if possible):
// Assume object $b has been defined & instantiated elsewhere
$funcName = $b->funcName; // Where funcName contains the string 'A'
$funcName::staticFunction();
The :: operator is used to refer to a non-instanced class. That means you're referencing either static methods or variables, or const. The hallmark of static methods is that they cannot work with your instance. They are, essentially, stand-alone functions and cannot use $this to refer back to the instance of your class.
As such, you cannot refer to a static method by referencing the instance. You need to use
self::function();
or
$this->function();
While the latter makes it look like it might be part of the instance, it's included for completeness only.
I created a class implementing ArrayAccess and I added it a function to prevents WRITE actions:
$Obj->Add("key","something");
$Obj->Add("key2","something2");
$Obj->SetReadOnly(); // sets read only property
unset($Obj["key2"]); // throws error, object is readonly
But, i want to prevent unsetting object too:
unset($Obj);
I can do it?I hear suggestions.
Thanks for help!.
I can't imagine any situation where you would really want to do this. I can also imagine doing this will cause you serious problems at script termination when all objects are destroyed. The PHP manual says the following on throwing exceptions in destructors:
Note:
Attempting to throw an exception from a destructor (called in the time
of script termination) causes a fatal error.
The above statement implies that you can throw an exception if you're not in the script termination phase, so maybe the following is possible.
public function __destruct ()
{
if ($this -> isReadOnly ())
{
throw new Exception ('Class is read-only');
}
}
However, as the manual points out, this will trigger a fatal error during script shutdown.
I honestly can't see any point to wanting to prevent object destruction. It should be up to the programmer to manage object lifetimes.
unset() does not actually destruct an object, if that's what you're trying to prevent.
An object will only be destructed when all references to it have been unset or are no longer in scope. Even then it won't happen until the garbage collector runs.
So if you have some code that you are worried will molest your object, you've already done a good job of making it immutable with your read-only logic.
Let's say you have
$Obj = gotMyObjectSomehow();
and you need to pass it to a some other code you don't want to unset $Obj. As long as that code is called inside a function, there's nothing to be concerned about. If you call
someFunction($Obj);
and let's say that function unsets the parameter it's passed in
function someFunction($anObj) {
unset($anObj);
}
then your original $Obj variable will still be set.
The function creates a second variable referencing the original object and uses that in its own scope.
You can't control unsetting variable names, because those names are not technically a part of the object referenced. Consider the following:
$a = new MyObject();
$b = $a;
Now you have two references to the same object. There is no difference between using $a and $b, because in PHP objects are always used by reference (i.e. you don't have to do $b =& $a in the second line). So both $a and $b are essentially the same object; unsetting $a will not destroy the object, as well as unsetting $b won't destroy it; all references need to be unset before the object is destroyed.
I don't think you can do what you're asking for - it's not possible to prevent a variable being unset like that.
However, a comment of yours above set me thinking. You said:
.... idea if you want to prevent unsets system variables in a thirdparty extensions
So if I understand you right, your aim here is to ensure that while the thirdparty code (ie your software) is in use, all the variables associated with it remain in place?
Now you haven't specified much about what variables there are in this system. We see one object in the question, but presumably there must be more than that? I'm guessing you've got a bunch of things that tie together, right? It would help in these sorts of questions to provide a bit more context; the actual thing that you're asking for isn't possible, but with a bit of understanding about what you want to achieve, we could come up with alternatives.
Okay. So my suggestion: create your objects as Singletons. This is often frowned on by purists, but might work well for this situation, depending on exactly what you're doing. The beauty here is that you can encapsulate all access to the object inside class methods, meaning that the developer using your code doesn't have access to the master copy of the object in order to unset it.
A singleton works like this:
<?php
class mySingletonClass {
private static $masterObject=null;
public static function getInstance() {
if(!isset(self::$masterObject)) {
self::$masterObject = new self;
}
return self::$masterObject;
}
private function __construct() {
//your existing constructor, as it already exists, but marked as private.
}
//...and all the other methods as you already have them.
}
The class constructor method is private, so can only be accessed from methods within the class. Therefore, you can no longer do new classname(). The only way you can get an object of this class is to get it from the static getInstance() method. And the key thing here is that this method always returns the same copy of the object.
$obj = mySingletonClass::getInstance();
unset($obj);
$obj = mySingletonClass::getInstance(); //will give the exact same object as the first time.
You can unset it if you want, but the original object is still there and can still be accessed. Any of your other classes can use that getInstance() method to get the same copy of the same object from anywhere in the program. It's an indestructible global variable.
Singletons are often used for a program's main database connection object, but it might be a useful pattern for you here.
I hope that helps. It's about the only way I can think of to get close to what you want.
Can I define a class constant inside the class constructor function ?
(based on certain conditions)
That goes against the idea of class constants - they should not be dependent on a specific instance. You should use a variable instead.
However, if you insist on doing this, are very adventurous and can install PHP extensions, you can have a look at the runkit extension that allows to modify classes and their constants at runtime. See this doc: http://www.php.net/manual/en/function.runkit-constant-add.php
I don't think you can.
It wouldn't make sense, either - a class constant can be used in a static context, where there is no constructor in the first place.
You'll have to use a variable instead - that's what they're there for.
Try look here:
http://php.net/manual/en/language.oop5.constants.php
http://php.net/manual/en/language.oop5.static.php
Hope this helps.
As far as standard instance constructors go, there is no way to do this, and as others have pointed out, it wouldn't make sense. These constructors are called per created object instance, at the point they are created. There is no guarantee this constructor would get called before some code tried to access the constant. It also doesn't make sense in that the code would get called over and over again each time a new instance was constructed, whereas a const should only get set once.
It would be nice if PHP either offered some kind of static constructor that let you set the value one time for uninitialized constants, or allowed more types of expressions when defining constants. But these are not currently features of PHP. In 2015 an RFC was made that proposed adding static class constructors, but it is, at the time of me writing this answer, still in the draft status, and has not been modified since 2017.
I think the best alternative for now is to not use constants in this kind of scenario, and instead use static methods that return the value you want. This is very simple in that it only uses the PHP language features as is (not requiring any special extensions), these static methods can be called in the standard way, and you don't need to hack the autoloading process to call some kind of initializer function that sets static variables. The method might need to rely on private static variables in order to make sure the same instance is returned every time, if an object instance is being returned. You would need to write the implementation of this method to be constant like in the sense that it will always return the same thing, but takes advantage of being able to do things you can't do with a constant, like return on object instance or rely on complex expressions or function calls. Here is an example:
final class User
{
/** #var DefinitelyPositiveInt|null */ private static $usernameMaxLength;
public static function getUsernameMaxLengthConst(): DefinitelyPositiveInt
{
if ($usernameMaxLength === null) {
$usernameMaxLength = new DefinitelyPositiveInt(40);
}
return $usernameMaxLength;
}
}
$usernameInput.maxLength = User::getUsernameMaxLengthConst();
This is still not a perfect solution because it relies on the programmer to write these in a constant like way when that is desired (always returning the same value). Also, I don't like that the best place to document the fact that it is a const is in the method name, thus making it even longer to call. I also don't like that you now have to call it as a method instead of just accessing a property, which would be syntactically nicer.
This example is essentially an implementation of a singleton, but sometimes the purpose of a singleton is to be a constant rather than just a singleton. What I mean is, you might want the instance to always exist, and it might be an immutable type (none of the properties are public or mutable, only having methods that return new objects/values).
I am sorry to break it to you but it is not possible in vanilla PHP.
I am not very sure about frameworks or extensions but I am sure that it is not possible in vanilla PHP.
I recommend you to use variables instead.
You still can't, but maybe some of these (progressively weirder) ideas (just ideas, not true solutions) will work for you:
(1) You could use a private property, with a public getter method. The property cannot be modified outside the class, such as constants, but unfortunately it is accessed as a method, not as a constant, so the syntax is not the same.
class aClass{
private $const;
function __construct($const){
$this->const=$const;
}
function const(){
return $this->const;
}
}
$var1=new aClass(1);
echo $var1->const(); //Prints 1
(2) If you really want this value to be accessed as constant from outside, you can use define () inside the constructor. Unfortunately it doesn't get tied to the class or object name (as it do when you use const, using for example myClass::myConst). Furthermore, it only works if you create a single instance of the class. The second object you create is going to throw an error for redefining the constant, because is untied.
class otherClass{
function __construct($const){
define('_CONST',$const);
}
function const(){
return _CONST;
}
}
$var2=new otherClass('2');
echo $var2->const(); //Prints 2
echo _CONST; //Prints 2
#$var3=new aClass('3'); //Notice: Constant _CONST already defined
echo _CONST; //Still prints 2!
(3) Perhaps that last problem can be solved by giving variable names to the constants, related to the object to which they belong. This may be a bit weird... but maybe it works for someone.
class onemoreClass{
private $name;
function __construct($const,$name){
$this->name=$name;
$constname=$this->name."_CONST";
define($constname,$const);
}
function const(){
return constant($this->name.'_CONST');
}
}
$name='var4';
$$name=new onemoreClass(4,$name);
echo $var4->const(); //Prints 4
echo var4_CONST; //Prints 4
$name='var5';
$$name=new onemoreClass(5,$name);
echo $var5->const(); //Prints 5
echo var5_CONST; //Prints 5
Why does PHP require you to explicitly write $this? I would understand if you had to use $this here:
function foo($bar) {
$this->bar = $bar;
}
But you must write it explicitly in verbose code that looks like this:
$this->var3 = globalFun($this->var, $this->var2[$this->anotherVar], $this->method());
as opposed to:
$var3 = globaFun($var, $var2[$anotherVar], method());
So what is the point of $this?
Additional Bonus Question:
Why do we have to differentiate static references and instances? Why do we need:
static function getValue() {
return self::value;
}
Can't PHP find out at runtime if the variable/method in question is static? Now if I want to change a method from static to non-static, I have to replace all those self:: with $this-> (and vice-versa).
Wouldn't it be better if we had a $this that behaves like it does in Java?
Since this was re-opened, I'll post here my answer, as promised.
TL;DR version If it were not required to qualify a member access, there would be not only performance penalties, but the same line of code could simultaneously signify a field access and a local variable access, depending on the code path.
Full version
In PHP, there's always one symbol table active at the table. This is either the global symbol table or a function/method local symbol table (which by the way, are lazily built). Superglobals and optimizations like compiled variables aside, when a variable $var is requested, it is looked up in the current symbol table. Since the object properties live not on the symbol tables, but instead on either in the objects (the instance properties) or the structure associated the class (the static properties), a lookup for $var can never return a property.
To bring a given variable to the function scope, you must explicitly signal your intention by creating a reference. Examples:
$myglobal = 7;
class A {
private $prop;
public function meth() {
global $myglobal; //bring a global to the current scope
$prop =& $this->prop; //brings a property to the current scope
$local = 4;
$lambda = function () use (&$local) { };
}
}
Obviously, this is just a more sophisticated way to phrase what currently happens. The question is why this behavior?
After all, in Java we only have to type this.prop when there's a local variable called prop hiding the property. Why is this not a good option for PHP?
I can think of several reasons.
The object properties are determined at runtime
PHP has something called "dynamic properties". You can assign new properties to objects at runtime. In fact given two objects of the same class, one can have a given property $a and while the other doesn't. Example:
$obj1 = new stdClass();
$obj2 = new stdClass();
$obj1->a = 7;
In PHP, the defined local variables are determined at runtime
Variables do not have to be declared; consequently, depending on the code path, at some point a variable may or may not be defined. To add insult to the injury, we also have the monster called "variable variables". Example:
class A {
private $g = 3;
public function func($varname) {
if (rand(1,2) == 1) {
$g = 4; //no block scope; the scope is the function's
}
$$varname = 5; //god knows what's happening here
//if local variables hid properties, we'd have trouble
}
}
In Java, a given identifier may also represent, inside the same function, a local variable and a property, but:
Not within the same block (in PHP, all blocks in a function share exactly the same scope).
You get a warning if you're hiding a property.
Crucially, in any given occurrence of an identifier, it's either a property or a local variable, it can't sometimes be one and other times the other.
Consequences
Owing to these facts, it would be impossible to determine at compile time if $var referred to a local variable or to a property. Consequently:
At runtime, every time a variable occurred, it would have to looked up first in the local symbol table, then in the instance properties table, and finally in the static properties list, or any other order (since there can't be an instance and a static property with the same name and static properties need to be declared, there would be some optimization potential here, but the point stands). This means a symbol would have, in the worst case, would have to be looked up in three different places. This is bad from a performance perspective.
A given symbol occurrence could mean different things on different occasions. This is a recipe for disaster.
Okay, so let's remove the need for writing $this everywhere. Take a look at this situation:
class Foo {
public function setBar($value) {
$bar = $value;
}
}
$foo = new Foo();
$foo->setBar('some value');
Is $bar a local variable or a member of $foo?
There has to be some differentiation. They could have allowed declaration of local variables with the var keyword, but that would not have been backwards-compatible and would have been very confusing to people upgrading from older versions of PHP.
Same thing applies to self::. How does the interpreter know whether the function you wanted to call is global or specific to the class?
PHP was not OOP.
Now it is, but with side effects.
Actually, I know people who use this. in Java even where unnecessary because they feel it creates clearer code ;) I don't have a really definite answer, but I guess that, internally, getting $var would always have to be translated to $this->var. So it's not like someone intentionally made things more complicated by forcing us to do $this->var, but just decided to not implement the $var shortcut. If that helps in any way, I don't know ;)