Can I freely assign something to non-existent or not-known-existent members in php? Is there any difference between member names and associative array index?
I there any difference beteween
$a = array();
$a['foo'] = 'something';
and
$a->foo = 'something';
If there is a difference, then how can I create "empty" object and add members dynamically to it?
You are mixing Arrays (which are bags/containers for data) and Objects (which are wrappings for data with semantic meaning and functionality).
Array Access
The first is correct since you are using an Array which acts like a HashTable or Dictionary in other languages.
$a = array(); // create an empty "box"
$a['foo'] = 'something'; // add something to this array
Object Access
The second is an Object access. You would use something like this:
class Foo {
public $foo;
}
$a = new Foo();
$a->foo = 'something';
Although the better usage in that case is to use a setter/getter approach like this.
class Foo {
private $foo;
public function setFoo($value) {
$this->foo = $value;
}
public function getFoo() {
return $this->foo;
}
}
$a = new Foo();
$a->setFoo('something');
var_dump($a->getFoo());
PHP Magic
However there is still an option to use PHPs Magic Methods to create a behavior like you describe it. Nevertheless this should be considers not the usual way of storing data to a object since this lead to errors and gives you a much harder time with (unit) testing.
class Foo {
private $data = array();
public function __set($key, $value) {
$this->data[$key] = $value;
}
public function __get($key) {
return $this->data[$key];
}
}
$a = new Foo();
$a->foo = 'something'; // this will call the magic __set() method
var_dump($a->foo) // this will call the magic __get() method
This hopefully did help you solving your problem.
If you want to assign arbitrary members to an object as you do on an associative array, you'll probably want to look into PHP's magic property overloading.
Here's an example class that will let you just assign and retrieve variables (mostly taken from the PHP documentation):
<?php
class PropertyTest
{
/** Location for overloaded data. */
private $data = array();
public function __set($key, $value) {
$this->data[$key] = $value;
}
public function __get($key) {
return $this->data[$key];
}
}
// sample use:
$a = new PropertyTest();
$a->foo = "bar";
echo $a->foo; // will print "bar"
?>
You can create an empty class object and afterwards add properties to it, e.g.:
<?php
$myObject = new StdClass();
$myObject->id = 1;
$myObject->name = "Franky";
$myObject->url = "http://www.google.com";
var_dump($myObject);
...this should produce
object(stdClass)#1 (3) { ["id"]=> int(1) ["name"]=> string(6) "Franky" ["url"]=> string(21) "http://www.google.com" }
Personally, I prefer using object classes instead of arrays.
Related
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);
I currently have the following __get/__set methods in the PHP class example:
class example{
/*Member variables*/
protected $a;
protected $b = array();
public function __get($name){
return $this->$name;
}
public function __set($name, $value){
$this->$name = $value;
}
}
However, in addition to setting standard protected variables, I would also like to be able to set protected arrays within the class. I've looked through some other questions and found general suggestions as to how to simple set variables using the __get/__set methods, but nothing that would allow one to use these magic methods to set BOTH arrays and nonarrays, i.e. in the following manner:
$fun = new $example();
$fun->a = 'yay';
$fun->b['coolio'] = 'yay2';
Simple, define __get() like so:
public function &__get($name)
{
return $this->$name;
}
// ...
$fun = new example();
$fun->a = 'yay';
$fun->b['coolio'] = 'yay2';
var_dump($fun);
Output:
object(example)#1 (2) {
["a":protected]=>
string(3) "yay"
["b":protected]=>
array(1) {
["coolio"]=>
string(4) "yay2"
}
}
Do take necessary care when you're dealing with references, it's easy to mess up and introduce hard to track bugs.
First, a quote from the ole' manual on ArrayAccess::offsetSet():
This function is not called in assignments by reference and otherwise indirect changes to array dimensions overloaded with ArrayAccess (indirect in the sense they are made not by changing the dimension directly, but by changing a sub-dimension or sub-property or assigning the array dimension by reference to another variable). Instead, ArrayAccess::offsetGet() is called. The operation will only be successful if that method returns by reference, which is only possible since PHP 5.3.4.
I'm a bit confused by this. It appears that this suggests that (as of 5.3.4) one can define offsetGet() to return by reference in an implementing class, thus handling assignments by reference.
So, now a test snippet:
(Disregard the absence of validation and isset() checking)
class Test implements ArrayAccess
{
protected $data = array();
public function &offsetGet($key)
{
return $this->data[$key];
}
public function offsetSet($key, $value)
{
$this->data[$key] = $value;
}
public function offsetExists($key) { /* ... */ }
public function offsetUnset($key) { /* ... */ }
}
$test = new Test();
$test['foo'] = 'bar';
$test['foo'] = &$bar; // Fatal error: Cannot assign by reference to
// overloaded object in
var_dump($test, $bar);
Ok, so that doesn't work. Then what does this manual note refer to?
Reason
I'd like to permit assignment by reference via the array operator to an object implementing ArrayAccess, as the example snippet shows. I've investigated this before, and I didn't think it was possible, but having come back to this due to uncertainty, I (re-)discovered this mention in the manual. Now I'm just confused.
Update: As I hit Post Your Question, I realized that this is likely just referring to assignment by reference to another variable, such as $bar = &$test['foo'];. If that's the case, then apologies; though knowing how, if it is at all possible, to assign by reference to the overloaded object would be great.
Further elaboration: What it all comes down to, is I would like to have the following method aliases:
isset($obj[$key]); // $obj->has_data($key);
$value = $obj[$key]; // $obj->get_data($key);
$obj[$key] = $value; // $obj->set_data($key, $value);
$obj[$key] = &$variable; // $obj->bind_data($key, $variable);
// also, flipping the operands is a syntactic alternative
$variable = &$obj[$key]; // $obj->bind_data($key, $variable);
unset($obj[$key]); // $obj->remove_data($key);
As far as has, get, set, and remove go, they're no problem with the supported methods of ArrayAccess. The binding functionality is where I'm at a loss, and am beginning to accept that the limitations of ArrayAccess and PHP are simply prohibitive of this.
What the manual is referring to are so called "indirect modifications". Consider the following script:
$array = new ArrayObject;
$array['foo'] = array();
$array['foo']['bar'] = 'foobar';
In the above script $array['foo'] = array(); will trigger a offsetSet('foo', array()). $array['foo']['bar'] = 'foobar'; on the other hand will trigger a offsetGet('foo'). Why so? The last line will be evaluated roughly like this under the hood:
$tmp =& $array['foo'];
$tmp['bar'] = 'foobar';
So $array['foo'] is first fetched by ref and then modified. If your offsetGet returns by ref this will succeed. If not you'll get some indirect modification error.
What you want on the other hand is the exact opposite: Not fetch a value by reference, but assign it. This would theoretically require a signature of offsetSet($key, &$value), but practically this is just not possible.
By the way, references are hard to grasp. You'll get lots of non-obvious behavior and this is especially true for array item references (those have some special rules). I'd recommend you to just avoid them altogether.
This does not work with ArrayAccess, you could add yourself a public function that allows you to set a reference to an offset (sure, this looks different to using array syntax, so it's not really a sufficient answer):
class Test implements ArrayAccess{
protected $_data = array();
public function &offsetGet($key){
return $this->_data[$key];
}
...
public function offsetSetReference($key, &$value)
{
$this->_data[$key] = &$value;
}
}
$test = new Test();
$test['foo'] = $var = 'bar';
$test->offsetSetReference('bar', $var);
$var = 'foo';
echo $test['bar']; # foo
$alias = &$test['bar'];
$alias = 'hello :)';
echo $var; # hello :)
Probably such a function was forgotten when ArrayAccess was first implemented.
Edit: Pass it as "a reference assignment":
class ArrayAccessReferenceAssignment
{
private $reference;
public function __construct(&$reference)
{
$this->reference = &$reference;
}
public function &getReference()
{
$reference = &$this->reference;
return $reference;
}
}
class Test implements ArrayAccess{
...
public function offsetSet($key, $value){
if ($value instanceof ArrayAccessReferenceAssignment)
{
$this->offsetSetReference($key, $value->getReference());
}
else
{
$this->_data[$key] = $value;
}
}
Which then works flawlessly because you implemented it. That's probably more nicely interfacing than the more explicit offsetSetReference variant above:
$test = new Test();
$test['foo'] = $var = 'bar';
$test['bar'] = new ArrayAccessReferenceAssignment($var);
$var = 'foo';
echo $test['bar']; # foo
$alias = &$test['bar'];
$alias = 'hello :)';
echo $var; # hello :)
I need to have a variable that only one function can write (let's call that function a) and that only one other function can read (let's call that function b). Is that possible?
You could use a static variable:
function foo($val=null) {
static $var = null;
if (!is_null($var)) $var = $val;
return $val;
}
Here $var is only visible inside the function foo and is maintained throughout multiple calls:
foo(123);
echo foo(); // 123
foo(456);
echo foo(); // 456
Or use a class with a private member and access/modify it with public methods:
class A {
private $var;
public function setVar($val) {
$this->var = $val;
}
public function getVar() {
return $this->var;
}
}
With this the private member var is only visible to a particular instance of this class:
$obj1 = new A();
$obj1->setVar(123);
$obj2 = new A();
$obj2->setVar(456);
echo $obj1->getVar(); // 123
echo $obj2->getVar(); // 456
If you make the member static, then there is just one for the class instead of for each instance:
class A {
private static $var;
public function setVar($val) {
self::$var = $val;
}
public function getVar() {
return self::$var;
}
}
$obj1 = new A();
$obj1->setVar(123);
$obj2 = new A();
$obj2->setVar(456);
echo $obj1->getVar(); // 456
echo $obj2->getVar(); // 456
You can use a static abstract class.
abstract class Settings
{
private static var $_settings = array();
public static function get($key,$default = false)
{
return isset(self::$_settings[$key]) ? self::$_settings[$key] : $default;
}
public static function set($key,$value)
{
self::$_settings[$key] = $value;
}
}
Example Usage:
Settings::set('SiteName',`SomeResult`);
echo Settings::get('SiteName');
Since 5.3.0, you can use anonymous functions as closures. The advantage here, is that you can hold on to b... which is returned by a... and fire it off when you're ready:
<?php
function a()
{
// Only a() can write to $myVar
$myVar = 42;
$b = function() use ($myVar)
{
// $b can read $myVar
// no one else can
return $myVar;
};
return $b;
}
// get $b
$test = a();
// use $b
echo $test();
?>
Another solution before 5.3.0, but here a has to fire b which may not be that practical:
You can simply create an internal variable and pass it as an argument. You can do this inside a class, or just inside simple functions:
function a()
{
// ...
// Write the variable that
// only this function can write to
$thisVar = 1;
b($thisVar);
//...
}
function b($myVar)
{
// ...
// Do stuff w $myVar, a copy of $thisVar
// Changing $myVar has no effect on $thisVar
//
}
Do you mean friend functions? Because I'd love to be able to do that. So far I haven't found an easy way though (although you could try using Reflection, but that seems like way to much effort).
For me, it usually hasn't been an issue of maintaining data integrity / encapsulation, but of keeping the list of public methods (which is kinda like a class's API) free of clutter. A perfect framework should be easy to use, have obvious function names etc etc etc. Methods intended for use by a single other method really mess things up. The "solution" I've taken to is prefixing those function names by one or two underscores and writing "intended for internal use only" or something to that extent in the comments.
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);