This question already has answers here:
PHP Readonly Properties?
(7 answers)
Closed last year.
When trying to change it,throw an exception.
I suppose a solution, for class properties, would be to :
not define a property with the name that interests you
use the magic __get method to access that property, using the "fake" name
define the __set method so it throws an exception when trying to set that property.
See Overloading, for more informations on magic methods.
For variables, I don't think it's possible to have a read-only variable for which PHP will throw an exception when you're trying to write to it.
For instance, consider this little class :
class MyClass {
protected $_data = array(
'myVar' => 'test'
);
public function __get($name) {
if (isset($this->_data[$name])) {
return $this->_data[$name];
} else {
// non-existant property
// => up to you to decide what to do
}
}
public function __set($name, $value) {
if ($name === 'myVar') {
throw new Exception("not allowed : $name");
} else {
// => up to you to decide what to do
}
}
}
Instanciating the class and trying to read the property :
$a = new MyClass();
echo $a->myVar . '<br />';
Will get you the expected output :
test
While trying to write to the property :
$a->myVar = 10;
Will get you an Exception :
Exception: not allowed : myVar in /.../temp.php on line 19
class test {
const CANT_CHANGE_ME = 1;
}
and you refer it as test::CANT_CHANGE_ME
Use a constant. Keyword const
The short answer is you can't create a read-only object member variable in PHP.
In fact, most object-oriented languages consider it poor form to expose member variables publicly anyway... (C# being the big, ugly exception with its property-constructs).
If you want a class variable, use the const keyword:
class MyClass {
public const myVariable = 'x';
}
This variable can be accessed:
echo MyClass::myVariable;
This variable will exist in exactly one version regardless of how many different objects of type MyClass you create, and in most object-oriented scenarios it has little to no use.
If, however, you want a read-only variable that can have different values per object, you should use a private member variable and an accessor method (a k a getter):
class MyClass {
private $myVariable;
public function getMyVariable() {
return $this->myVariable;
}
public function __construct($myVar) {
$this->myVariable = $myVar;
}
}
The variable is set in the constructor, and it's being made read-only by not having a setter. But each instance of MyClass can have its own value for myVariable.
$a = new MyClass(1);
$b = new MyClass(2);
echo $a->getMyVariable(); // 1
echo $b->getMyVariable(); // 2
$a->setMyVariable(3); // causes an error - the method doesn't exist
$a->myVariable = 3; // also error - the variable is private
I made another version that uses #readonly in the docblock instead of private $r_propname. This still doesn't stop the declaring class from setting the property, but will work for public readonly access.
Sample Class:
class Person {
use Readonly;
/**
* #readonly
*/
protected $name;
protected $phoneNumber;
public function __construct($name){
$this->name = $name;
$this->phoneNumber = '123-555-1234';
}
}
The ReadOnly trait
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$refProp = new \ReflectionProperty($this, $prop);
$docblock = $refProp->getDocComment();
// a * followed by any number of spaces, followed by #readonly
$allow_read = preg_match('/\*\s*\#readonly/', $docblock);
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
See the source code & test on my gitlab
I cooked up a version, too, using a trait.
Though in this case, the property can still be set by its declaring class.
Declare a class like:
class Person {
use Readonly;
protected $name;
//simply declaring this means "the 'name' property can be read by anyone"
private $r_name;
}
And this is the trait I made:
trait Readonly {
public function readonly_getProperty($prop){
if (!property_exists($this,$prop)){
//pretty close to the standard error if a protected property is accessed from a public scope
// throw new \Error("Property '{$prop}' on class '".get_class($this)."' does not exist");
trigger_error('Undefined property: '.get_class($this).'::\$'.$prop,E_USER_NOTICE);
}
$allow_read = property_exists($this, 'r_'.$prop );
if ($allow_read){
$actual = $this->$prop;
return $actual;
}
throw new \Error("Cannot access non-public property '{$prop}' of class '".get_class($this)."'");
}
public function __get($prop){
return $this->readonly_getProperty($prop);
}
}
See the source code & test on my gitlab
I know this is an old question, but PASCAL's answer really helped me and I wanted to add to it a bit.
__get() fires not only on nonexistent properties, but "inaccessible" ones as well, e.g. protected ones. This makes it easy to make read-only properties!
class MyClass {
protected $this;
protected $that;
protected $theOther;
public function __get( $name ) {
if ( isset( $this->$name ) ) {
return $this->$name;
} else {
throw new Exception( "Call to nonexistent '$name' property of MyClass class" );
return false;
}
}
public function __set( $name ) {
if ( isset( $this->$name ) ) {
throw new Exception( "Tried to set nonexistent '$name' property of MyClass class" );
return false;
} else {
throw new Exception( "Tried to set read-only '$name' property of MyClass class" );
return false;
}
}
}
Related
I'm trying to hide all the class variable from Symfony's dumper dd,
App\Namespace\Class1 {#NNN ▼
-requestCollection: []
-requestDumpOptions: []
-responseCacheTime: -1
-responseCode: -1
-responseData: ""
-responseRawData: ""
}
I was tried to use class magic method __debugInfo but it append the dump object instead of replacing it
Code :
...
public function __debugInfo()
{
return ['copyright' => 'SOME COPYRIGHT'];
}
...
Result :
App\Namespace\Class1 {#NNN ▼
-requestCollection: []
-requestDumpOptions: []
-responseCacheTime: -1
-responseCode: -1
-responseData: ""
-responseRawData: ""
copyright: "SOME COPYRIGHT"
}
My goal is to only show copyrigth field in the dump
App\Namespace\Class1 {#NNN ▼
copyright: "SOME COPYRIGHT"
}
Using dump again on __debugInfo works but it return Array instance instead of App\Namespace\Class1
...
public function __debugInfo()
{
dd(['copyright' => 'SOME COPYRIGHT']);
}
...
I found your question interesting & tried a way out, if I am correct you don't want to allow dd() to access you private & protected properties.
For that I have created a custom function in my app/helpers.php,
I named it mydd(), this is how it looks,
if (! function_exists('mydd') ){
function mydd($obj=null, ...$args){
foreach($args as $arg){
if(isset($obj) && is_object($obj)){
$reflect = new ReflectionObject($obj);
if(in_array($reflect->getProperty($arg), $reflect->getProperties(ReflectionProperty::IS_PRIVATE | ReflectionProperty::IS_PROTECTED))){
throw new RuntimeException("Cannot access private or protected properties");
}
}
\Symfony\Component\VarDumper\VarDumper::dump($arg);
}
die(1);
}
}
It will do your work, but there are certain changes from dd(), you need to pass the object along with the property, if not then pass null as first argument to the function mydd(), because I guess there is nothing that can find object instance of a class from its property.
I have used Reflection Class Object for that. So basically it will check if the given property of the same object passed as first argument is either private or protected & throw runtime exception.
My Testing
Created a class called DDRepository,
<?php
namespace App\Repository;
class DDRepository{
private $foo = 'foo';
public $bar = 'bar';
private $shoo = 'shoo';
protected $too = 'too';
public function __construct(){
}
public function getFoo(){
mydd($this,$this->too);
}
}
Then called it from web.php
Route::get('checkdd', function(){
$ddrepo = new \App\Repository\DDRepository;
$ddrepo->getFoo();
});
throws Exception as shown in pic,
I have understood your concern. First of all, let me discuss about the variable scope private. private variable is private in the current class. If you use the class in another class private variable will not work. So, you have to use another class to protect your private variable.
<?php
class ParentClass
{
private $yourPrivateVariable = "yourPrivateVariable";
}
class Class1 extends ParentClass
{
private $username = "api";
public function doSomething(){
// write code using private variable
}
}
class NewClass
{
public function doSomething()
{
$test = new Class1();
$test -> doSomething();
return $this->public = "Joe";
}
}
$test = new NewClass();
$test->doSomething();
var_dump($test);
//output
//object(NewClass)#1 (1) {
// ["public"]=>
// string(3) "Joe"
//}
?>
now you can see that non of private variables are showing in result.
I have an abstract class like this :
<?php
abstract class NoCie {
const SC = 01;
const MTL = 02;
const LAV = 03;
}
?>
I would like to test if a variable $x contain value from this abstract class only.
For now i used $x instanceof NoCie but this is not working probably because this class is abstract and can't be instantiated.
Here is the code that i'm trying to use to validate.
class CustomersTaxes
{
public $NoCie;
private $_file;
public function __construct($file)
{
$this->_file = $file;
}
public function CheckValidAndWrite()
{
$error = false;
//Numéro de compagnie
if (!($this->NoCie instanceof NoCie)) {
$error = true;
}
}
}
Here is my code that instantiate this class :
$t = new CustomersTaxes($_SERVER['DOCUMENT_ROOT'] . '/test.xlsx');
$t->NoCie = NoCie::SC;
$t->NoClient = "d";
$t->CheckValidAndWrite();
How can i do that?
I think you are confusing two concepts, but maybe what you want can be achieved in some other way. The only thing I can think of right now is to use PHP method type-hinting. But I would refactor slightly, making the NoCie property protected to be manipulated only by a getter and a setter. Like this:
class CustomersTaxes
{
private $NoCie;
private $_file;
public function __construct($file)
{
$this->_file = $file;
}
public function getNoCie()
{
return $this->NoCie;
}
public function setNoCie(NoCie $NoCie)
{
$this->NoCie = $NoCie::VALUE;
}
}
You still need a class that extends the abstract one, though, otherwise it'll never work:
class SCA extends NoCie
{
const VALUE = '01';
}
Since the NoCie property on CustomersTaxes is private, you have to set it a bit differently:
$t = new CustomersTaxes($_SERVER['DOCUMENT_ROOT'] . '/test.xlsx');
$t->setNoCie(new SCA());
// ...
That way you can make sure that whenever a NoCie property is set, it will be the class you want. No need to validate -- if setNoCie is triggered by an invalid value, it'll throw an exception.
I figured out another way to do this job without type hinting. Type hinting seem to be a good way but need to much files to work with psr-4 autoloader.
My choice is to use ReflectionClass to get all constant as array and compare value from $this->SC.
$NoCieReflection = new \ReflectionClass('\\Ogasys\\Enum\\NoCie');
if (!in_array($this->NoCie, $NoCieReflection->getConstants())) {
$error = true;
array_push($msg, "# de compagnie invalide");
}
I am in the process of re educating myself in the programming of OOP
in PHP.
I have been under the erroneous assumption that all variables not static inside a class def
had to be proceeded with $this->. I finally stumbled over myself with a variable naming collision
and am now relearning, somewhat, the way of php OOP.
My present question is:
Can you mark a method property set with $this-> as public, private, or protected?
I have done;
class _DEMO
{
private $this->someVar = 'whatever';
}
and I get a syntax error.
then:
class _DEMO
{
public function __construct($_ip)
{
$this->ip = $_ip; // <<< how can I set access on this property?
}
}
As of now I don't know how to use properties with access levels set
other than to declare static properties.
OK, so I tried
class _DEMO
{
public $_someVar = 'so and so';
}
$_a = new _DEMO()
print $_a->someVar // NADA
So, I take it that the variable can be declared this way but not initialized?
on second thought, OOPs! I saw the problem with variable reference $_testy and $this->testy, should be $this->_testy
class _DEMO
{
private static $_doTell = 'Well??...';
private $_testy = "So What? ";
public function __construct($_ip)
{
$this->_testy .= " right Now?";
$this->ip = $_ip;
$this->soWhat = 'Boo!...';
}
public function getVar($_var, $_addon)
{
$this->setVar($_var, $_addon);
switch($_var)
{
case 'soWhat':
return $this->soWhat;
break;
case 'ip':
return $this->ip;
break;
case 'doTell':
return self::$_doTell;
break;
default:
break;
}
}
private function setVar($_var, $_input)
{
switch($_var)
{
case 'soWhat':
$this->soWhat .= $_input;
break;
case 'ip':
$this->ip .= $_input;
break;
case 'doTell':
self::$_doTell .= $_input;
break;
default:
break;
}
}
}
$_test = new _DEMO('Hello??...');
print "Test result 1: ".$_test->ip;
print "<br>Test result 2: ".$_test->getVar('doTell', ' So, how old are you??');
print "<br>Test result 3: \$_test::\$_doTell; Fatal error: Cannot access private property _DEMO::$_doTell";
print "<br>Test result 4: ".$_test->testy; // <<<< prints " right Now?" without errors about trying to
//access private members
Properties are declared at the start of the class like:
public $property1;
private $property2;
protected $property3;
Then,
1. all properties can be accessed from any method of the same class like $this->property.
2. $property1 can be accessed from all methods in ANY class.
3. $property2 can be accessed from all methods in the SAME class.
4. $property3 can be accessed from all methods in the SAME class and classes which EXTEND THE SAME class.
E.g. You can access a private property from a public method:
class A {
private $property;
public function getProperty() {
return $this->property
}
Then, in a controller, you could do:
$obj = new A;
$property = $obj->getProperty();
but not:
$obj = new A;
$property = $obj->property;
Sure, you can:
class MySampleClass {
/**
* #var string
*/
private $aStringVariable;
/**
* #param string $anIP
*/
protected function __construct( $anIP ) {
$this->aStringVariable = $anIP;
}
}
// How to use the class
$myClassObject = new MySampleClass( '192.168.0.1' );
You might wish to download the eval-version of PHPStorm, a PHP IDE. The IDE might help, since it points out errors while you type and PHPStorm provides auto-completion.
Additionally, have a look in the PHP Wiki of StackOverflow. The Wiki provides valuable information.
Here is a simple example of using property.
class HelloProperty{
private $mySimpleProperty;
public function setSimpleProperty($mySimpleProperty){
$this->mySimpleProperty = $mySimpleProperty;
}
public function getSimpleProperty(){
return $this->mySimpleProperty;
}
}
$obj = new HelloProperty();
$obj->setSimpleProperty('Hello Property');
echo $obj->getSimpleProperty();
:)
Why can't I get access to my "incSessionCount" function inside my "newSession" function?
class Session {
private $_num_session = 0;
private function incSessionCount() {
$this->_num_session++;
}
public static function newSession($key, $value) {
if( !isset( $_SESSION[$key] ) ) {
$_SESSION[$key] = $value;
$this->incSessionCount();
return true;
} else {
return false;
}
}
}
I just played around, like making incSessionCount() public and so on...
And then I thought, that it must be even accessible, when it's set to private ...
It's possible, that I missed a useful article, which should have helped me, but finally I ended up asking.
So why doesn't this work?
The problem is that your newSession is static, thus you are not supposed to call instance methods from it.
I suppose you're trying to do:
Session::newSession($key, $value);
instead of
$session = new Session();
$session->newSession($key, $value);
The error is not because you're calling a private method from within a public one, but because you're using $this instead of self.
$this special variable represents the current instance object while self represents the class itself.
If you enable error display and set error reporting level to E_ALL, you will see the problem is about using $this in a wrong context.
See below theses little modifications to do what you want, and check theses pages about
class Session {
private $_num_session = 0;
private static $inst = null;
public static function instance(){
if (!static::$inst)
static::$inst = new Session();
return static::$inst;
}
private function incSessionCount() {
$this->_num_session++;
}
public static function newSession($key, $value) {
if( !isset( $_SESSION[$key] ) ) {
$_SESSION[$key] = $value;
Session::getInstance()->incSessionCount();
return true;
} else {
return false;
}
}
}
You can look for design pattern and singleton on internet, and use magic __clone() to forbid more than one instance
I only found the german version of the documentation, I don't know why : http://de.php.net/manual/de/language.oop5.patterns.php
EDIT: Check this link about design patterns : http://www.phptherightway.com/pages/Design-Patterns.html
Remember, that static methods are binded with the class. Non-static methods are binded with the instance (when you do something like $instance = new MyClass();). But when you call something on the static context, you don't have to have any instance.
It's the same, when you want to call something on instance($this), because in static context doesn't exist any instance.
The problem is the public method is static and you are trying to use a method for an instantiated object. In a static method the $this variable refers to other static methods and properties only.
I have a reoccuring problem that I am currently tackling like so -
a POST variable coming in to the script which has a platform, the platform is from a list such as: xbox,ps3,pc,mobileapp,mobilegame etc
for each different platform I want to be able to do something different in my script but in some cases I want code to do very similar things at the moment I do something like this:
$platformArray = array(
'ps3'=>array('displayName'=>'playstation 3','function'=>'funcPS3'),
'xbox'=>array('displayName'=>'Xbox','function'=>'funcXbox')
)
//similar amongst all platforms code on line below
echo 'you have a :'.$platformArray[$_POST['platform']]['displayName'].' for playing games';
call_user_func($platformArray[$_POST['platform']['function']);
function funcPS3(){
echo 'ps3 specific code';
}
function funcXbox(){
echo 'xbox specific code';
}
I want to move towards a OOP approach in my code, I want to use objects as my data storage medium rather than arrays as I'm doing now, but I do sometimes need to define attributes in the code ahead of time, how could I do the above but with objects?
I would recommend for you to start by understanding polymorphism. This lecture should be good start.
When you are trying to create behavior, based on some flag, you should implement two classes with same interface:
class Xbox
{
private $displayName = 'XBox 360';
public function identify()
{
// Xbox-specific stuff
return ':::::::::::'. $this->displayName;
}
}
class PS3
{
private $displayName = 'Playstation 3';
public function identify()
{
// playstation-specific stuff
return '+++'. $this->displayName . '+++';
}
}
The two classes have method with same name that would do different things;
$platform = $_POST['platform'];
// classes in PHP are case-insensitive
// expected values would be: xbox, Xbox, ps3, pS3
if ( !class_exists($platform) )
{
echo "Platform '{$platform}' is not supported";
exit;
// since continuing at this point would cause a fatal error,
// better to simply exit
}
$object = new $platform;
echo $object->identify();
Basically, in this case you really do not care, which type of platform you are working with. All you need to know is that they both have same public interface. This is called "polymorphic behavior".
I'm going to work from a very naive OO version, to what is considered "good" OO code, using polymorphic behavior and avoiding global state.
1. Not polymorphic and has global static data
This is pretty bad because it is really just a wrapper object over procedural code. It needs a map of functions to call for each type of platform.
class Platform {
private static $platformArray = array(
'ps3' => array(
'displayName'=>'playstation 3',
'function'=>'funcPS3'
),
'xbox' => array(
'displayName'=>'Xbox',
'function'=>'funcXbox'
)
);
private $type;
public function __construct($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
$this->type = $type;
}
public function printCode() {
// This was a question embedded within your question, you can use
// http://php.net/manual/en/function.call-user-func.php
// and pass an instance with a method name.
return call_user_func( array($this, self::$platformArray[$this->type]) );
}
private function funcPS3(){
echo 'ps3 specific code';
}
private function funcXbox(){
echo 'xbox specific code';
}
}
$plat = new Platform($_POST['platform']);
$plat->printCode();
2. Polymorphic... but it still uses global data
By creating a base class you can implement behavior in subclasses, creating separate class for each concern. The big problem here is that subclasses need to register with a global registry.
abstract class Platform {
abstract protected function getCode();
public function printCode() {
echo $this->getCode();
}
private function __construct() {} // so only factory can instantiate it
private static $platformArray = array();
public static function create($type) {
if (!array_key_exists($type, self::$platformArray)) {
throw new Exception("Invalid Platform type $type" );
}
return new self::$platformArray[$type];
}
public static function addPlatform($type, $ctor) {
if (!is_subclass_of($ctor, 'Platform')) {
throw new Exception("Invalid Constructor for Platform $ctor" );
}
self::$platformArray[$type] = $ctor;
}
}
class PlatformXBox extends Platform{
protected function getCode() {
return 'xbox specific code';
}
}
Platform::addPlatform('xbox', 'PlatformXBox');
class PlatformPs3 extends Platform {
protected function getCode() {
return 'ps3 specific code';
}
}
Platform::addPlatform('ps3', 'PlatformPs3');
$plat = Platform::create($_POST['platform']);
$plat->printCode();
3. Polymorphic, no global data
By putting your code into a namespace, you avoid the static code in the base class and avoid the dangers of mapping post parameters directly into classes.
namespace platform {
interface IPlatform {
public function getDisplayName();
public function getCode();
}
class PlatformFactory {
static public function create($platformType) {
$className = "\\platform\\$platformType";
if ( !is_subclass_of($className, "\\platform\\IPlatform") ){
return null;
}
return new $className;
}
}
class Xbox implements IPlatform {
public function getDisplayName(){
return 'xbox';
}
public function getCode(){
return 'xbox code';
}
}
class Ps3 implements IPlatform {
public function getDisplayName(){
return 'ps3';
}
public function getCode(){
return 'ps3 code';
}
}
}
Now you can use those classes like the following
$platform = platform\PlatformFactory::create('xbox');
echo $platform->getCode() ."\n" ;
$platform2 = platform\PlatformFactory::create('ps3');
echo $platform2->getDisplayName()."\n";
$noPlatform = platform\PlatformFactory::create('dontexist');
if ($noPlatform) {
echo "This is bad, plaftorm 'dontexist' shouldn't have been created";
} else {
echo "Platform 'dontexist' doesn't exist";
}
You might want to create a class called platforms and within the class a different method for each platform:
class platforms {
//Create your variables here, also called properties.
public $displayName;
//Create a function, also called a method for each platform you intent to use.
public function xboxPlatform(){
//Code comes here what you want to do.
}
}
Hope this helps.