I want to have a class property that allow for an expression to take place on the right side of the equals sign. All versions of PHP choke on the following code, but it is written in this way to allow for easier extendibility in the future.
/* Example SDK Class */
class SDK
{
/* Runtime Option Flags */
// Strings
# 0: Makes no change to the strings.
var $STRING_NONE = (1 << 0);
# 1: Removes color codes from the string.
var $STRING_STRIP_COLOR = (1 << 1);
# 2: Removes language codes from the string.
var $STRING_STRIP_LANG = (1 << 2);
# 3: Removes all formatting from the string.
var $STRING_STRIP = SELF::STRING_STRIP_COLOR & SELF::STRING_STRIP_LANG;
# 4: Converts color codes to HTML & UTF-8.
var $STRING_HTML = (1 << 3);
# 8: Converts color codes to ECMA-48 escape color codes & UTF-8.
var $STRING_CONSOLE = (1 << 4);
# 16: Changes player names only.
var $STRING_NAMES = (1 << 5);
# 32: Changes host names only.
var $STRING_HOSTS = (1 << 6);
function SDK($fString = SELF::STRING_HTML & SELF::STRING_NAMES & SELF_HOST)
{
// constructor code.
}
}
$SDK &= new SDK(SDK::STRING_NONE);
(1 << 0) seems like very basic syntax to me, and is not fathomable why PHP would not allow for such a thing. Can anyone think of a work around that would maintain readability and future expandability of the following code?
When declaring a class constant or property in PHP you can only specify a primitive values for default values. So for instance, this class declaration won't work:
class TEST {
const ABC = 2 * 4;
const DEF = some_function();
static $GHI = array(
'key'=> 5 * 3,
);
}
But this class declaration will:
class TEST {
const ABC = 8;
static $GHI = 15;
}
These rules apply to default values for class constants/properties - you can always initialize other variables with the results of an expression:
$a= array(
'a'=> 1 * 2,
'b'=> 2 * 2,
'c'=> 3 * 2,
);
The reason for this class declaration behavior is as follows: expressions are like verbs. They do something. Classes are like nouns: they declare something. A declarative statement should never produce the side-effects of an action statement. Requiring primitive default values enforces this rule.
With this in mind we can refactor the original class as follows:
class SDK
{
static protected $_types= null;
static public function getType($type_name) {
self::_init_types();
if (array_key_exists($type_name, self::$_types)) {
return self::$_types[$type_name];
} else {
throw new Exception("unknown type $type_name");
}
}
static protected function _init_types() {
if (!is_array(self::$_types)) {
self::$_types= array(
'STRING_NONE'=> 1 << 0,
// ... rest of the "constants" here
'STRING_HOSTS'=> 1 << 6
);
}
}
function __construct($fString = null) {
if (is_null($fString)) {
$fString= self::getType('STRING_NONE') & self::getType('STRING_HOSTS');
}
var_dump($fString);
}
}
$SDK &= new SDK(SDK::getType('STRING_HOSTS'));
PHP class property can not have an expression in it's 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. [...]
Source: PHP Manual: Properties
The following code would work fine, as it's information can be ascertained at compile time and does not require the compiler to look anywhere else, do any mathematical or string functions get the information.
class goodExample
{
// These are OK.
var $Var = 1;
const consts = 'I\'m a Constant Property of the class goodExample.';
static $static = array(FALSE, TRUE);
}
The following, on the other hand, is not valid as it's value must be parsed to get it's true value. This is not valid in PHP at all. The var $Var requires a mathematical operation. The const consts requires concations and a varable look up to get it's value so that's two reasons why that one would not work. Lastly, the static property $static requires two more mathematical operations to get it's true value.
class badExample
{
// These are NOT OK.
var $Var = 1 + 1;
const consts = "I'm a constant property of the class " . __CLASS__ . '.';
static $static = array((1 << 0), (1 << 2));
}
Consts & Static Keywords
Once you declare these properties, their value can not change.
const
[...] Constants differ from normal variables in that you don't use the $ symbol to declare or use them.
The value must be a constant expression, not (for example) a variable, a property, a result of a mathematical operation, or a function call.
Its also possible for interfaces to have constants.
Source: PHP Manual: Class Constants
static
[...] Declaring class properties or methods as static makes them accessible without needing an instantiation of the class. A property declared as static can not be accessed with an instantiated class object (though a static method can).
Source: PHP Manual: Static Keyword
Static can be used outside of the class, so long as you reference the class name.
I think your problem is just with all the vars that you have in there. They should probably all be replaced by const. var was necessary in PHP 4 for declaring member variables, but causes an error in PHP 5. You should be using const, public, etc as of PHP 5 instead.
PHP is able to do (1 << 0) the problem is just because you are using var when you should be using const. Also, doing self::VARIABLE is meant for static variables, and I believe it should be self::$variable (unless you define them as const
Related
I've a question about function declaration. I would like have something like:
password_hash('er', PASSWORD_ARGON2I);
https://www.php.net/manual/en/function.password-hash.php
When I create a function, I would like declare some possibilities like:
PASSWORD_DEFAULT = 1
PASSWORD_BCRYPT = 2
PASSWORD_ARGON2I = 3
PASSWORD_ARGON2ID = 4
Then, when the user use the function he just set the value of the constant or the constant.
Actually I do:
$instanceOfMyClass->myFunction('er', MyClass::A_CONSTANT);
It done the job, but I'm forced to write the class name before use the constant name. Then if I do thaht I've access to all the constant of the class.
I think, I talk about something like constants at the function level.
Thanks a lot :-)
PS: Is there a way to do a DeclarationType like int|object|string for a function attribute ? Actually I don't type hint in this case, and for object, I need to specify a class or an Interface, sometimes, I accept many object to call the __toString() magic class, then I accept all objects.
I don't really understand your issue
constants like PASSWORD_DEFAULT, but just it (no function name with double :).
but my guess is, you are looking for define().
I suggest you to utilize OOP as much as possible and so like others said, use class constants. But if you want global constants for whatever reason, it goes like this:
define('DIRECTION_FORWARD', 0);
define('DIRECTION_LEFT', 1);
define('DIRECTION_RIGHT', 2);
function goInDirection($direction = DIRECTION_FORWARD)
{
// ....
}
Instead of just sequential numbers as values, you can use bitmasks, which work with bitwise operators and the power of 2.
https://www.php.net/manual/en/language.operators.bitwise.php
There are many ways of doing it.
the defined constants can not be modified, but they can be analyzed and verified if a condition is met; although this one is doing all that; since it would be double work for something immutable / immutable
<?php
class Main{
const CONSTANT = 'value constant';
function ouputConts() {
echo var_dump(self::CONSTANT);
}
}
in your case; that I can keep in a constant:
<?php
class Main{
const CONSTANT1 = 1; //integer
const CONSTANT2 = 0.1345; //float
const CONSTANT3 = 'text'; //string
const CONSTANT4 = array(1,2,3,4,5,10,20); //array
const CONSTANT5 = true; //bollean
function ouputConts() {
echo var_dump(self::CONSTANT1);
echo var_dump(self::CONSTANT2);
echo var_dump(self::CONSTANT3);
echo var_dump(self::CONSTANT4);
echo var_dump(self::CONSTANT5);
}
}
because in php an object or any type of object is considered mutable; you can not declare constants of type objects.
Documentation:
https://php.net/manual/en/language.constants.syntax.php
https://www.php.net/manual/en/language.oop5.constants.php
So your other question is whether you can pass a constant as a property of a function:
Yes, but you should have some considerations:
If you are in the same class it is not necessary you can use the value in any function of the same class, without needing to pass it is Global for the class / Scope:
class Main{
const CONSTANT = 'value constant';
function function1() {
$this->function2();
}
function function2() {
$this->function3();
}
function function3() {
$this->function4();
}
function function4() {
echo var_dump(self::CONSTANT);
}
}
If the function belongs to another class I recommend you first store it in a variable ...:
class Main1{
const CONSTANT = 'value constant';
function function1() {
$foo = CONSTANT;
Main2::FunctionTest($foo);
}
}
class Main2{
function FunctionTest($foo = '') {
echo var_dump($foo);
}
}
// I can not verify it.
I hope I have helped you in your question; if you need more help or deepen your query leave me a comment.
Update; i see this commnet:
#ChristophBurschka By doing that, when you use the function you don't
have help for retrieve specific constant (all class consant are not
available for this function). When you use native PHP function, you
just have possibilities to use available constant and the IDE have the
capatibilities to find them (and it list it to you automatically when
you start to fill the corresponding argument)? Thanks for your answer
about Type hint :-)
if the problem is around the scope of your const:
You can declare global constants in your applications (out of all classes and functions):
<?php
if (!defined('DB_PASS')) {
define('DB_PASS', 'yourpassword');
}
It will be available in any class and function of the whole application. and its use is not to be feared, since they are IMMUTABLE CONSTANTS.
Update recovery and implementation
With the last way I explain, you can then make a comparison and define which data you want to use; if the data of the constant or the data sent by the user; but all this must be tied to an analysis of the user's data in a specific function that you define.
Last Update
With the new define you can this:
<?php
//declaration:
if (!defined('PASS_SIS')) {
define('PASS_SIS', array(
'PASSWORD_DEFAULT' => 1,
'PASSWORD_BCRYPT' => 2,
'PASSWORD_ARGON2I' => 3,
'PASSWORD_ARGON2ID' => 4,
));
}
//call Function
$instanceOfMyClass->myFunction('er');
//function
function myFunction('er'){
echo var_dump(PASS_SIS);
//Access to data:
echo PASS_SIS['PASSWORD_DEFAULT'];
}
Taken from the PHP manual:
Like any other PHP static variable, static properties may only be
initialized using a literal or constant; expressions are not allowed.
So while you may initialize a static property to an integer or array
(for instance), you may not initialize it to another variable, to a
function return value, or to an object.
So this means I can't do the following because a) expressions are not allowed and b) function return values are not allowed.
class MyClass {
// I can't do this.
public static $var = 10 * 2;
// I can't do this.
public static $sum = array_sum( array( 3, 5, 6 ) );
}
But, with reference to b) function return values not allowed, why then is it possible to do the following when array() is a function with a return value?
class MyClass {
// I can do this.
public static $array = array( 3, 5, 6 );
}
array() is not a function, it's an initializer. Unlike ordinary functions, it's interpreted at compile time, so it can be used to initialize a static.
For the reference, this is what is allowed after the static keyword:
static_scalar_value:
common_scalar (e.g. 42)
static_class_name_scalar (Foo::class)
namespace_name (Foo)
T_NAMESPACE T_NS_SEPARATOR namespace_name (namespace \Foo)
T_NS_SEPARATOR namespace_name (\Foo)
T_ARRAY '(' static_array_pair_list ')' e.g. array(1,2,3)
'[' static_array_pair_list ']' e.g. [1,2,3]
static_class_constant e.g. Foo::bar
T_CLASS_C (__CLASS__)
http://lxr.php.net/xref/PHP_5_5/Zend/zend_language_parser.y#945
Php 5.6 adds "static operations" to this list, which makes it possible to use expressions for statics, as long as these ultimately resolve to static scalars.
class X {
static $foo = 11 + (22/11); // syntax error in 5.5, valid in 5.6
}
A good question! The simple ansvar is that array() only looks like a function, but in reality isn't one.
From the PHP documentation:
Note:
array() is a language construct used to represent literal arrays, and not a regular function.
Because an array is not a function.
While array(1,2) looks like you are calling a function that is called array, you are doing none of the sorts. You are just making an array, which is not a function call. it is closer to saying $a = 1.
SO,
The problem
It's not well-known, but PHP allows to compare objects - and not just on equality == - but on < and > too. But - how it works? So if I want to create comparable objects - what restrictions/rules they should follow?
Most useful case is with DateTime() objects - they hold certain timestamp and they could be compared (and this has logical sense). On lxr there's some explanation for DateTime . But what about common case?
I have:
class C
{
protected $holder;
protected $mirror;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//false, false, true: used $holder
var_dump($one>$two, $one==$two, $one<$two);
-if I'll change properties declaration order, it will use $mirror:
class C
{
//only order changed:
protected $mirror;
protected $holder;
public function __construct($h = null)
{
$this->holder=$h;
$this->mirror=-1*$h;
}
}
$one = new C(1);
$two = new C(2);
//true, false, false: used $mirror
var_dump($one>$two, $one==$two, $one<$two);
So it seems one of the 'rules' is that it will use first declared property. But why is it using protected property at all is not clear to me too.
Now, more complex sample:
class Test
{
protected $a;
protected $b;
function __construct($a, $b)
{
$this->a = $a;
$this->b = $b;
}
}
$x = new Test(1, 2);
$y = new Test(1, 3);
// true, false, false
var_dump($x < $y, $x == $y, $x > $y);
$x = new Test(3, 1);
$y = new Test(2, 1);
// false, false, true
var_dump($x < $y, $x == $y, $x > $y);
-so it will use first not-equal property for comparison. But code snippets above are only some cases. I want to know exactly how it's happening and why. Thus,
Question
Is: how it works? I mean, more detailed:
Can I rely on fact, that PHP will use first not-equal property for comparison?
What will be done if count of properties isn't equal? (i.e. some property was dynamically added to instance during code execution)
Can I treat protected/private properties as to be counted for such comparison always?
e.t.c. - so if there are some additional conditions/restrictions/rules that will affect result - please, post. Documentation states only for ==/=== comparison. Also, comparison of instances of different classes is out of the issue since it'll return false (obviously).
PHP compares sequentially (in the order of declaration) the object properties and stops at the first inequal property found. This behavior is not documented, so there's not much to be said about it, sadly, other than looking at the source of PHP.
Not documented is usually a synonym of "don't rely on it".
Each class in php has an associated structure (in the c code) of handler functions, it looks like
struct _zend_object_handlers {
/* general object functions */
zend_object_add_ref_t add_ref;
zend_object_del_ref_t del_ref;
[...]
zend_object_compare_t compare_objects;
[...]
};
compare_objects points to a function that "takes two objects" and returns -1,0,1 according to whatever this comparator defines as the order (just like strcmp() does for strings).
This function is used only when both operands (objects) point to the same comparision function - but let's just stick with this case.
That's where e.g. DateTime "adds" its feature to compare two DateTime instances, it just defines another, DateTime-specific compare_objects function and puts it in the structure describing its class.
static void date_register_classes(TSRMLS_D)
{
[...]
INIT_CLASS_ENTRY(ce_date, "DateTime", date_funcs_date);
ce_date.create_object = date_object_new_date;
[...]
date_object_handlers_date.compare_objects = date_object_compare_date;
So if you want to know (exactly) how two DateTime instances are compared, take a look at date_object_compare_date.
The comparision described in the manual (at least for the case cmp(o1,o2)==0) seems to be implemented in zend_std_compare_objects. And it's used by both StdClass and a simple user defined class like e.g.
<?php
class Foo { }
$a = new StdClass;
$b = new Foo;
$a > $b;
But other classes (in php extensions) do set other functions. DateTime, ArrayObject, PDOStatement, even Closures use different functions.
But I haven't found a way to define a comparision function/method in script code (but haven't looked too hard/long)
The exact behavior is defined in the PHP language specification, thus, you can rely on it.
[…] if the objects are of different types, the comparison result is FALSE. If the objects are of the same type, the properties of the objects are compares [sic] using the array comparison described above.
And the array comparison is defined as follows:
[…] For arrays having the same numbers of elements, the keys from the left operand are considered one by one, if the next key in the left-hand operand exists in the right-hand operand, the corresponding values are compared. If they are unequal, the array containing the lesser value is considered less-than the other one, and the comparison ends; otherwise, the process is repeated with the next element. […] If all the values are equal, then the arrays are considered equal.
Simply exchange every mention of array with object and key with property in your mind and you have the exact description of how this works. I omitted useless array specifics in above quote.
I'd like to be able to do something like this:
class Circle {
const RADIUS_TO_CIRCUMFERENCE = M_PI * 2; // Not allowed
private $radius;
public function __construct( $radius ) {
$this->radius = $radius;
}
...
public function getCircumference() {
return $this->radius * self::RADIUS_TO_CIRCUMFERENCE;
}
}
But I can't create a class constant from an expression like that:
The value must be a constant expression, not (for example) a variable, a property, a result of a mathematical operation, or a function call.
So my question is: What's the best workaround for this limitation of PHP? I'm aware of the following workarounds, but are there any others which are better?
1. Create a property
class Circle {
private static $RADIUS_TO_CIRCUMFERENCE;
private $radius;
public function __construct( $radius ) {
$this->radius = $radius;
$this->RADIUS_TO_CIRCUMFERENCE = M_PI * 2;
}
...
public function getCircumference() {
return $this->radius * $this->RADIUS_TO_CIRCUMFERENCE;
}
}
I don't like this, because the value of $RADIUS_TO_CIRCUMFERENCE can be changed, so it's not really a "constant".
2. Use define()
define( 'RAD_TO_CIRCUM', M_PI * 2 );
class Circle {
const RADIUS_TO_CIRCUMFERENCE = RAD_TO_CIRCUM;
...
public function getCircumference() {
return $this->radius * self::RADIUS_TO_CIRCUMFERENCE;
}
}
This is better, since the value is truly constant, but the drawback is that RAD_TO_CIRCUM has been globally defined.
A digression
I don't understand how this can work. (Edit: I've tested it, and it does work.) According to the Handbook of PHP Syntax:
The const modifier creates a compile-time constant and so the compiler will replace all usage of the constant with its value. In contrast, define creates a run-time constant which is not set until run-time. This is the reason why define constants may be assigned with expressional values, whereas const requires constant values which are known at compile-time.
The manual confirms that "constants defined using the const keyword ... are defined at compile-time".
In this bug report from 3 years ago, a member of the PHP team wrote:
For the class constant we need a constant value at compile time and can't evaluate expressions. define() is a regular function, evaluated at run time and can therefore contain any value of any form.
But in my example above, the value of RAD_TO_CIRCUM is not known at compile-time. So what is the compiler putting for the value of RADIUS_TO_CIRCUMFERENCE?
I'm guessing that the compiler creates some kind of placeholder for the value of RADIUS_TO_CIRCUMFERENCE, and at run-time, that placeholder gets replaced with the value of RAD_TO_CIRCUM. Might this placeholder be a kind of resource? If so, maybe this technique should be avoided? The manual says: "It is possible to define constants as a resource, but it should be avoided, as it can cause unexpected results."
3. Create a method
class Circle {
...
private static function RADIUS_TO_CIRCUMFERENCE() {
return M_PI * 2;
}
public function getCircumference() {
return $this->radius * $this->RADIUS_TO_CIRCUMFERENCE();
}
}
This is my favourite workaround that I'm aware of. The value is constant, and it doesn't affect the global space.
Is there another workaround which is even better?
As of PHP 5.6, you can use math expressions in PHP constants, so this would work:
const RADIUS_TO_CIRCUMFERENCE = M_PI * 2;
I encountered this thread because my environment wasn't configured properly (was set to PHP 5.4 by accident), so don't forget to check your PHP version.
I would recommend this approach:
class Circle {
...
private function RADIUS_TO_CIRCUMFERENCE() {
static $RADIUS_TO_CIRCUMFERENCE;
if ( null === $RADIUS_TO_CIRCUMFERENCE )
$RADIUS_TO_CIRCUMFERENCE = M_PI * 2;
return $RADIUS_TO_CIRCUMFERENCE;
}
public function getCircumference() {
return $this->radius * $this->RADIUS_TO_CIRCUMFERENCE();
}
}
The goal is calculation only once for all class entities, like a real constant.
In my case,
my installed php version is 7.1 but, in my IDE(PhpStorm) settings my php version 5.4 was selected. Once i've changed my php version the problem was gone.
PhpStorm settings->PHP and change the PHP language level and CLI interpreter php version.
If you agree to use a standard variable instead of const keyword :
class Foo {
public $CONST_A; // = calculation A // To let people quickly see the value.
public $CONST_B; // = calculation B
public static function initClass() {
self::$CONST_A = /* calculation A */;
self::$CONST_B = /* calculation B */;
}
}
Foo::initClass();
initClass() is done only once, when the class file is required.
class Foo
{
public $var ;
function __construct($value)
{
$this->var = $value ;
}
}
$myFoo = new Foo('hello');
echo $myFoo->var . '<br>' ; // output : hello
// Question : how can I prevent another programer from accidentaly doing the following
$myFoo = 4 ;
echo $myFoo ; // output : 4
my question is in the comment // Question :...
I would like my coworkers being able to assign values to $myFoo use only $myFoo->var (or whatever public mutators are available in the class Foo)
thank you
EDIT :
with all respect to the users who claim it is not possible, SPL_Types PECL extention was able to achieve that (to a certain degree) see e.g http://php.net/manual/en/class.splint.php or http://blog.felixdv.com/2008/01/09/spl_types-in-php-and-strong-typing/
You cannot do this in any weakly typed language. If you have functions that take this variable as an argument, you can use type hinting in PHP, but otherwise you cannot prevent people from re-assigning their variables.
This is true to an extent even for strongly typed languages. If a programmer creates two instances of a class, there is no mechanism to prevent them from assigning a different instance to a variable of the same type.
The only way this can happen is if the programmer explicitly uses constants instead of variables (such as using things like final in Java, or val in Scala, etc), but, either way, you have no control over it in any language.
You cannot prevent changing the type within the class, but if you make it protected or private and then add a setVariable() method (where Variable is your variable name) you can control the input. Something like:
class myClass {
protected $integer = 0;
public function setInteger($new_value)
{
if (!is_int($new_value)) {
throw new RuntimeException('Cannot assign non-integer value to myClass::$integer');
}
$this->integer = $new_value;
}
public function getInteger()
{
return $this->integer;
}
}
// Now, outside of the class, the $integer property can only be changed using setInteger()
$class = new myClass;
$class->setInteger('dog'); // Uncaught Runtime exception ...
$class->setInteger(5);
echo $class->getInteger(); // 5
An alternative version of that function would accept string numbers and convert them to integers:
public function setInteger($new_value)
{
if (!is_numeric($new_value)) {
throw new RuntimeException('Cannot assign non-integer value to myClass::$integer');
}
$this->integer = (int) $new_value;
}
$class->setInteger('5'); // 5