I have some questions about the implementation of implementing ArrayAccess in PHP.
Here is the sample code:
class obj implements arrayaccess {
private $container = array();
public function __construct() {
$this->container = array(
"one" => 1,
"two" => 2,
"three" => 3,
);
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
Questions:
I am not asking why we do have to implement ArrayAccess since I am assuming it is special interface that PHP Engine recognizes and calls the implemented inherited functions automatically?
Why are we declaring the implemented function public? Since I assume they are special functions called automatically. Shouldn't they be private since when calling saying $obj["two"] the functions are not be called from outside.
Is there a special reason to assign the filled-array in __constructor function? This is the constructor function I know but in this case what kind of help it is being of.
What's the difference between ArrayAccess and ArrayObject? I am thinking the class I implemented by inheriting the ArrayAccess doesn't support iteration?
How could we implement object-indexing without implementing ArrayAccess?
Thanks...
Correct
Because the interface defines them as public, therefore you have to also
You don't have to write the constructor that way if you don't want to*
ArrayAccess is an interface, ArrayObject is a class (which itself implements ArrayAccess)
No other way that I'm aware of
* Your constructor could look like this
public function __construct( array $data ) {
$this->container = $data;
}
Related
I need to be able to set my object like this:
$obj->foo = 'bar';
then I need to use it as an array like that:
if($obj['foo'] == 'bar'){
//more code here
}
Just add implements ArrayAccess to your class and add the required methods:
public function offsetExists($offset)
public function offsetGet($offset)
public function offsetSet($offset, $value)
public function offsetUnset($offset)
See http://php.net/manual/en/class.arrayaccess.php
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
Try extending ArrayObject
You'll also need to implement a __get Magic Method as Valentin Golev mentioned.
Your class will need to looks something like this:
Class myClass extends ArrayObject {
// class property definitions...
public function __construct()
{
//Do Stuff
}
public function __get($n) { return $this[$n]; }
// Other methods
}
ArrayObject implements the ArrayAccess interface (and some more). Using the ARRAY_AS_PROPS flag it provides the functionality you're looking for.
$obj = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
$obj->foo = 'bar';
echo $obj['foo'];
Alternatively you can implement the ArrayAccess interface in one of your own classes:
class Foo implements ArrayAccess {
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetSet($offset , $value) {
$this->$offset = $value;
}
public function offsetUnset($offset) {
unset($this->$offset);
}
}
$obj = new Foo;
$obj->foo = 'bar';
echo $obj['foo'];
You can access PHP object as PHP array, but in different ways. Try this:
$obj->{'foo'}
That is similar with accessing array like this:
$arr['foo']
You can also do this:
$propertyName = 'foo';
$obj->$propertyName; // same like first example
You'll have to implement the ArrayAccess interface to be able to do that -- which only means implementing a few (4 to be exact) simple methods :
ArrayAccess::offsetExists : Whether or not an offset exists.
ArrayAccess::offsetGet : Returns the value at specified offset.
ArrayAccess::offsetSet : Assigns a value to the specified offset.
and ArrayAccess::offsetUnset : Unsets an offset.
There is a full example on the manual's page I pointed to ;-)
You're mixing objects and arrays. You can create and access an object like so:
$obj = new stdClass;
$obj->foo = 'bar';
if($obj->foo == 'bar'){
// true
}
and an array like so:
$obj = new Array();
$obj['foo'] = 'bar';
if($obj['foo'] == 'bar'){
// true
}
You can define a class and add implements ArrayAccess if you want to access your class as both an array and a class.
http://www.php.net/manual/en/language.oop5.php
Your object must implement the ArrayAccess interface, then PHP will allow you to use the square brackets like that.
You could also cast the object as an array:
if((array)$obj['foo'] == 'bar'){
//more code here
}
Enhance Class capability with no functionality drawbacks
You can also use ArrayAccess to access a single array property in your class and leave other properties being accessed in OOP way. Yet still it will work as you requested.
class Foo implements \ArrayAccess
{
/**
* mixed[] now you can access this array using your object
* like a normal array Foo['something'] = 'blablabla'; echo Foo['something']; ... and so on
* other properties will remain accessed as normal: $Foo->getName();
*/
private myArrayOptions = [];
private $name = 'lala';
...
public function offsetExists($offset)
{
return isset($this->myArrayOptions[$offset]);
}
public function offsetGet($offset)
{
if ($this->offsetExists($offset)) {
return $this->myArrayOptions[$offset];
}
return null; // or throw the exception;
}
public function offsetSet($offset, $value)
{
$this->myArrayOptions[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->myArrayOptions[$offset]);
}
public function getName()
{
return $this->name;
}
public function __set($offset, $value){
$this->myArrayOptions[$offset] = $value;
}
...
}
The above will work as you expected.
$obj->foo = 'bar';
if($obj['foo'] == 'bar'){
echo "WoWo";
}
Also note that Foo['name'] !== Foo->getName()
those a two different variables
I implement ArrayAccess in some of my classes and often, I use almost exactly the same code for the ArrayAccess methods in my class as are in Example #1 on the ArrayAccess docs.
Since the code is the same, it would be nice to write this once as a Trait, and then my classes can just implement ArrayAccess (as now), and use ArrayAccessTrait without needing to duplicate a bunch of ArrayAccess methods.
The only thing preventing this is that I usually don't want my underlying array named $container, but something else more pertinent to the class I'm building.
So my question is, is there a way to "alias" whatever array property name I'm using within my own class with the $container being used in my ArrayAccessTrait ?
The example below has a Foo class that shows what I want; this example works, but I'd like to be able to use a property named something other than $container within the Foo class.
trait ArrayAccessTrait {
private $container = [];
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Foo implements ArrayAccess {
use ArrayAccessTrait;
private $container = [];
public function __construct() {
$this->container = [
"one" => 1,
"two" => 2,
"three" => 3,
];
}
public function hello($msg = 'Hello World') {
echo $msg . "<br/>\n";
}
}
$obj = new Foo;
$obj->hello();
echo "Array accessing element ['two'] = " . $obj['two'];
If you're defining private attributes in a trait, you arguably shouldn't be referring to them directly in the classes that implement it. Instead, just use the methods in the trait. I.e., instead of doing this in the concrete class:
$this->container["one"] = 1;
Do this:
$this["one"] = 1;
Then you don't need to know what the storage attribute is called, and you don't need to write any code around setting/getting it. Note also that you do not need to redefine private $container = []; in your class, as it already comes with the trait.
My question revolves arround magic methods.
This is a little example:
$context = new Context('Entities.xml');
$user_array = $context->Users;
$user = $context->Users->find(array('name' => 'John Smith'));
The second line returns an array with all user objects in it. The third line returns only the User object of the user called John Smith.
I wondered if this would be possible, the tricky part is that I don't know the properties of the Context class. They are generated from an xml file the user supplies upon instantiation and are accessable through magic getters and setters.
Context example(not complete, just to give an idea):
class Context {
private $path, $entities;
public function __construct($path) {
$this->path = $path;
}
public function __get($name) {
return $entities[$name];
}
public function __set($name, $arg) {
$entities[$name] = $arg;
}
}
Because I really needed a solution I implemented the following solution.
The getters of the Context class return a ResultLayer class that handles the results.
Example:
class ResultLayer implements IteratorAggregate {
public $data = array();
private $entity, $context;
public function __construct($context, $entity) {
$this->context = $context;
$this->entity = $entity;
}
public function getIterator() {
return new ArrayIterator($this->data);
}
public function get($index) {
return $this->data[$index];
}
public function toArray() {
return $this->data;
}
public function find($properties) {
return $this->context->getEntity($this->entity, $properties);
}
}
I have implemented the IteratorAggregate interface so that you can use a foreach loop to for example go through $Context->Users this makes the code more readable.
If anyone has a better approach, I'm still open to it. Any help is much appreciated!
This question already has answers here:
Magic __get getter for static properties in PHP
(6 answers)
Closed 9 years ago.
I'm trying to convert array to object. I want to use magic methods - __get and __set with static properties.
My code:
class UserData {
private static $id, $name, $login;
public function __get($var)
{
return self::$var;
}
public function __set($var, $val)
{
self::{$var} = $val;
}
}
And setting:
foreach($userArray as $key => $val)
{
DaneBilingowe::${$key} = $val;
}
Error:
Fatal error: Cannot access private property UserData::$id
Is it possible to use magic method with static property?
In short, no.
__get() and __set() are instance methods. They are essentially the functions that make up the stdClass(), which is an instance.
If you must set static content in this manner you can give the class a stdClass parameter and a singleton structure that would allow you to magically set and get data.
For example:
class UserData {
protected static $_instance;
protected $_data = array();
public static function get_instance() {
static $initialized = FALSE;
if ( ! $initialized) {
self::$_instance = new UserData;
$initialized = TRUE;
}
return self::$_instance;
}
public function __get($var) {
$self = self::get_instance();
return isset($self->_data[$var]) ? $self->_data[$var] : NULL;
}
public function __set($var, $val) {
$self = self::get_instance();
$self->_data[$var] = $val;
}
}
Then you could go:
$UserData =& UserData::get_instance();
$UserData->var = 'val';
echo $UserData->var; // prints 'val'
I don't recommend using Singletons in PHP, however, because they are pointless. You can read some reasons why in the post Best practice on PHP singleton classes.
Either use a static class or an instance class.
Magic getters and setters are shortcuts. You can implement the same behavior with normal setters and getters. The example below provides the same functionality, but the intent is a lot more clear:
class UserData {
protected $id, $name, $login;
public static function set_name($name) {
self::$name = $name;
}
public static function set_login($login) {
self::$login = $login;
}
public static function get_id() {
return self::$id;
}
public static function get_name() {
return self::$name;
}
public static function get_login() {
return self::login;
}
}
Notice how in the above code $id is not writable. It is only readable. $name and $login are readable and writable. It is easier and less buggy to control reading and writing using normal setters and getters. Magic methods are just that, magic, and usually magic is not concrete and is less understandable in code.
The final point I want to make is, why would UserData be static? Unless you only have 1 user in the entirety of your code it doesn't make sense to have it static. Perhaps I am not getting the whole picture, but something with an id and name should be instantiated so that you can have multiple instances. Otherwise, why have the id because the class itself is unique.
If you really want to use magic methods on static properties, you can but you will need an instance. Though it does not look reasonable, being a programmer itself is not reasonable at all :)
Also user defined classes and objects are not dynamic in php.
You can not add variables to them that easily... So you can use the pattern below:
class UserData {
private static $id, $name, $login, $arr = [];
public function __get($var){
return (array_key_exists(self::$arr, $var)? self::$arr[$var]:null;
}
public function __set($var, $val){
self::$arr[$var] = $val;
}
}
And setting: Well what is DaneBilingowe? I do not now here... But:
$inst = new UserData();
foreach($userArray as $key => $val){
$inst->$key = $val;
}
will work.
But beware, It will work only on class (static) memory.
Also since there is no appropriate filtering for setting names, weird things can happen.
(That means you should add them)
normally using java. I ve seen a snippet like this today
$oStrategie = new Strategie();
foreach($aData as $key=>$value) {
$oStrategie[$key] = $value;
}
$oStrategie->doSomething()
Strategie is a selfmade php class with nothing special. simple constructor doing nothing important and so on.
in the class Strategie the method doSomething() accesses the ArrayValues of $aData
$this['array_index_1']
Why can i access the array there even if the Strategie class doesent have any attributes defined and no setter overwritten or something like that? Can anybody explain me whats happening there? Is there no need to have attributes in the class in php???
Your class implements the ArrayAccess interface. This means it implements the following methods:
ArrayAccess {
abstract public boolean offsetExists ( mixed $offset )
abstract public mixed offsetGet ( mixed $offset )
abstract public void offsetSet ( mixed $offset , mixed $value )
abstract public void offsetUnset ( mixed $offset )
}
This allows you to use array access $var[$offset] on instances of this class. Here's a standard implement of a class like this, using a $container array to hold properties:
class Strategie implements ArrayAccess {
private $container = array();
public function __construct() {
$this->container = array(
"something" => 1,
);
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
Without looking at the actual implementation of Strategie or the class it's derived from, it's hard to tell what it's actually doing.
But using this, you can control the behavior of the class, for example, when accessing an offset that doesn't exist. Suppose we replace offsetGet($offset) with:
public function offsetGet($offset) {
if (isset($this->container[$offset])) {
return $this->container[$offset];
} else {
Logger.log('Tried to access: ' + $offset);
return $this->default;
}
}
Now whenever we try to access an offset that doesn't exist, it will return a default (eg: $this->default) and log an error, for example.
Note that you can accomplish similar behavior using the magic methods __set(), __get(), __isset() and __unset(). The difference between the magic methods I just listed and ArrayAccess is that you'd access a property via $obj->property rather than $obj[offset]