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]
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
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!
I'm playing with some design patterns, and wanted to create an example using the SPL's observer pattern. Because it doesn't make sense to have observers and subjects be completely generic, I wanted to extend the interfaces to make them more specific to the application at hand. The problem is that when I run the code below, I get errors like "DataAccess::update() must be compatible with that of SplObserver::update()".
I know that I can make this code execute without errors by switching the method signatures to match those of the interfaces. My question is this: Why doesn't it allow children of the classes defined in the signatures? Below, ModelObserver is a SplObserver, and Model is a SplSubject. I would have expected this to work. Am I missing something?
FYI, I know I could use the explicit method signatures as defined in the interface and use the instanceof keyword in my code logic to achieve the same thing. I was just hoping to find a more elegant solution. Thanks!
<?php
interface ModelObserver extends SplObserver {
}
class DataAccess implements ModelObserver {
/*
* (non-PHPdoc) #see SplObserver::update()
*/
public function update(Model $subject) {
// TODO Auto-generated method stub
}
}
// Just a generic model for the example
class Model implements SplSubject {
private $_properties = array ();
private $_observers = array ();
/*
* generically handle properties you wouldn't want to do it quite like this
* for a real world scenario
*/
public function __get($name) {
return $this->_properties [$name];
}
public function __set($name, $value) {
$this->_properties [$name] = $value;
}
public function __call($method, $args) {
if (strpos ( $method, 'get' ) === 0) {
$name = lcfirst ( str_replace ( 'get', '', $method ) );
return $this->_properties [$name];
}
if (strpos ( $method, 'set' ) === 0) {
$name = lcfirst ( str_replace ( 'set', '', $method ) );
$this->_properties [$name] = $args [0];
return $this;
}
}
public function __toString() {
return print_r ( $this, true );
}
/*
* (non-PHPdoc) #see SplSubject::attach()
*/
public function attach(ModelObserver $observer) {
$this->_observers [] = $observer;
return $this;
}
/*
* (non-PHPdoc) #see SplSubject::detach()
*/
public function detach(ModelObserver $observer) {
if (in_array ( $observer, $this->_observers )) {
$f = function ($value) {
if ($value != $observer) {
return $value;
}
};
$observers = array_map ( $f, $this->_observers );
}
return $this;
}
/*
* (non-PHPdoc) #see SplSubject::notify()
*/
public function notify() {
foreach ($this->_observers as $observer) {
$observer->update($this);
}
}
}
$da = new DataAccess();
$model = new Model ();
$model->setName ( 'Joshua Kaiser' )->setAge ( 32 )->setOccupation ( 'Software Engineer' )
->attach($da);
echo $model;
Limiting DataAccess::update() to accept your child Model breaks the contract of this interface.
True, all Model objects are of class SplSubject , but not all SplSubject are of class Model. An interface is a contract guaranteeing that an implementing class it supports everything the interface supports.
Your code, if it worked would be limiting the DataAccess::update() method to only the Model sub class and not the wider parent class SplSubjects . You cannot narrow the scope of the parameter passed to method defined by an interface.
Let's say you added a property public $foo to the Model class. If it were allowed, you could in your DataAccess::update() method you uses that property $foo. Someone could come along and extend SplSubjects to a child OddModel which didn't have a $foo property. They could no longer pass the OddModel into your DataAccess::update() function--if they could it would break as no $foo property exists for the OddModel.
This is the whole idea behind interfaces, by implementing they you agree 100% to support what is defined by the interface. In this case your interface says:
if you implement me, you must accept every SplSubject or class that extends SplSubject
You're implementation of the interface attempts to break the contract.
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;
}
Is there something like struct data type in PHP?
Can anyone give me example for struct data type to understand this better?
If there's no such data type, how can I get a data type that behaves like a struct?
Closest you'd get to a struct is an object with all members public.
class MyStruct {
public $foo;
public $bar;
}
$obj = new MyStruct();
$obj->foo = 'Hello';
$obj->bar = 'World';
I'd say looking at the PHP Class Documentation would be worth it.
If you need a one-off struct, use the StdObject as mentioned in alex's answer.
You can use an array
$something = array(
'key' => 'value',
'key2' => 'value2'
);
or with standard object.
$something = new StdClass();
$something->key = 'value';
$something->key2 = 'value2';
I recommend 2 things. First is associative array.
$person = Array();
$person['name'] = "Joe";
$person['age'] = 22;
Second is classes.
Detailed documentation here: http://php.net/manual/en/language.oop5.php
This is quite high up on Google so I figured I'd share my implementation of a pseudo-struct using PHP8 syntax. The toArray() method does depend on Illuminate\Support\Str to transform keys to snake case (useful for mass assignment against Laravel models) however just remove it if it doesn't fit your use-case.
The base class:
<?php
namespace App\Infrastructure\Structs;
use App\Infrastructure\Exceptions\CannotMutateStructException;
use App\Infrastructure\Exceptions\ClassPropertyNotFoundException;
use Illuminate\Support\Str;
use ReflectionClass;
abstract class Struct
{
/**
* #param string $name
* #param mixed $value
* #throws CannotMutateStructException
*/
public function __set(string $name, mixed $value): void
{
throw new CannotMutateStructException(
'Structs are immutable. If you need mutable data then use a class instead.'
);
}
public function all(): array
{
$reflector = new ReflectionClass(static::class);
$response = [];
foreach ($reflector->getProperties() as $property) {
$response[$property->name] = $this->{$property->name};
}
return $response;
}
public function toArray(bool $snakeCase = false): array
{
$all = self::all();
if ($snakeCase === false) {
return $all;
}
$snakeCaseAll = [];
foreach ($all as $key => $value) {
$snakeCaseAll[Str::snake($key)] = $value;
}
return $snakeCaseAll;
}
}
How to use:
<?php
namespace App\Infrastructure\Structs;
class Person extends Struct
{
public function __construct(
public string $name,
public int $age,
public int $heightInCentimetres,
) {}
}
How to interact with it:
>>> $t = new \App\Infrastructure\Structs\Person('Max', 26, 182);
>>> $t->age
=> 26
>>> $t->age = 40
App\Infrastructure\Exceptions\CannotMutateStructException with message 'Structs are immutable. If you need mutable data then use a class instead.'
>>> $t->toArray(true)
=> [
"name" => "Max",
"age" => 26,
"height_in_centimetres" => 182,
]
Hopefully this helps someone.
Edit: with PHP8.1 we now have readonly properties which can make this even more concise.
Edit: with PHP8.2 we now have an even more concise way of doing this. I'd highly recommend reading this article in its entirety for a good example: https://stitcher.io/blog/evolution-of-a-php-object
However the general jist is to write readonly class Foo {} which removes to need to add it against each property.
I cobbled together a 'dynamic' struct class today, had a look tonight and someone has written something similar with better handling of constructor parameters, it might be worth a look:
http://code.activestate.com/recipes/577160-php-struct-port/
One of the comments on this page mentions an interesting thing in PHP - apparently you're able to cast an array as an object, which lets you refer to array elements using the arrow notation, as you would with a Struct pointer in C. The comment's example was as follows:
$z = array('foo' => 1, 'bar' => true, 'baz' => array(1,2,3));
//accessing values as properties
$y = (object)$z;
echo $y->foo;
I haven't tried this myself yet, but it may be that you could get the desired notation by just casting - if that's all you're after. These are of course 'dynamic' data structures, just syntactic sugar for accessing key/value pairs in a hash.
If you're actually looking for something more statically typed, then ASpencer's answer is the droid you're looking for (as Obi-Wan might say.)
It seems that the struct datatype is commonly used in SOAP:
var_dump($client->__getTypes());
array(52) {
[0] =>
string(43) "struct Bank {\n string Code;\n string Name;\n}"
}
This is not a native PHP datatype!
It seems that the properties of the struct type referred to in SOAP can be accessed as a simple PHP stdClass object:
$some_struct = $client->SomeMethod();
echo 'Name: ' . $some_struct->Name;
Only associative arrays are structs in PHP.
And you can't make them strict on their own.
But you can sort of fake structure strictness with classes and interfaces, but beware that unlike structures, class instances are not passed in arguments, their identifiers are!
You can define a struct through an interface (or at least close to it)
Structs enforce a certain structure on an object.
PHP (<= 7.3) does not have native structs, but you can get around it with interfaces and type hinting:
interface FooStruct
{
public function name() : string;
}
interface BarStruct
{
public function id() : int;
}
interface MyStruct
{
public function foo() : FooStruct;
public function bar() : BarStruct;
}
Any class implementing MyStruct will be a MyStruct.
The way it's build up is not up to the struct, it just ensures that the data returned is correct.
What about setting data?
Setting struct data is problematic as we end up with getters and setters and it's something that is close to an anemic object or a DTO and is considered an anti-pattern by some people
Wrong example:
interface FooStruct
{
public function getName() : string;
public function setName(string $value) : FooStruct;
}
interface BarStruct
{
public function getId() : int;
public function setId(int $value) : BarStruct;
}
interface MyStruct
{
public function getFoo() : FooStruct;
public function setFoo(FooStruct $value) : MyStruct;
public function getBar() : BarStruct;
public function setBar(BarStruct $value) : MyStruct;
}
Then we end up with class implementations that might be mutable, and a struct must not mutate, this is to make it a "data type", just like int, string.
Yet there's no way to restrict that with interfaces in PHP, meaning people will be able to implement your struct interface in a class that is not a struct.
Make sure to keep the instance immutable
Also a struct may then be instantiated without the correct data and trigger errors when trying to access the data.
An easy and reliable way to set data in a PHP struct class is through its constructor
interface FooStruct
{
public function name() : string;
}
interface BarStruct
{
public function id() : int;
}
interface MyStruct
{
public function foo() : FooStruct;
public function bar() : BarStruct;
}
class Foo implements FooStruct
{
protected $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function name() : string
{
return $this->name;
}
}
class Bar implements BarStruct
{
protected $id;
public function __construct(string $id)
{
$this->id = $id;
}
public function id() : int
{
return $this->id;
}
}
class My implements MyStruct
{
protected $foo, $bar;
public function __construct(FooStruct $foo, BarStruct $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
public function foo() : FooStruct
{
return $this->foo;
}
public function bar() : BarStruct
{
return $this->bar;
}
}
Type hinting using interfaces: (if your IDE supports it)
If you don't mind not having the strict type checking, then another way would be using interfaces or classes with comments for the IDE.
/**
* Interface My
* #property Foo $foo
* #property Bar $bar
*/
interface My
{
}
/**
* Interface Foo
* #property string|integer $id
* #property string $name
*/
interface Foo
{
}
/**
* Interface Bar
* #property integer $id
*/
interface Bar
{
}
The reason to use interfaces instead of classes is for the same reason why interfaces exist in the first place, because then many classes with many implementations can have this same structure and each method/function that uses it will support every class with this interface.
This depends on your IDE, so you might need to use classes instead or just live without it.
Note:
Remember that you have to validate/sanitize the data in the instance elsewhere in the code to match the comment.
A public class is one option, if you want something more encapsulated you can use an abstract/anonymous class combination. My favorite part is that autocomplete still works (for PhpStorm) for this but I don't have a public class sitting around.
<?php
final class MyParentClass
{
/**
* #return MyStruct[]
*/
public function getData(): array
{
return array(
$this->createMyObject("One", 1.0, new DateTime("now")),
$this->createMyObject("Two", 2.0, new DateTime("tommorow"))
);
}
private function createMyObject(string $description, float $magnitude, DateTime $timeStamp): MyStruct
{
return new class(func_get_args()) extends MyStruct {
protected function __construct(array $args)
{
$this->description = $args[0];
$this->magnitude = $args[1];
$this->timeStamp = $args[2];
}
};
}
}
abstract class MyStruct
{
public string $description;
public float $magnitude;
public DateTime $timeStamp;
}