An object that can be used as an array as well - php

All over the place I have an array with a few elements, for example:
$myItem = [ 'a' => 10, 'b' => 20 ]
But but I would like to replace it with a class
$myClass = new MyOwnClass( 10, 20 );
$a = $myClass->GetSomeValue(); // this is the old 'a'
$b = $myClass->GetSomeOtherValue(); // this is the old 'b'
but for practical reasons I still want to be able to call
$a = $myClass['a'];
$b = $myClass['b'];
Is something like that possible in php?

Therefore, there is an interface named ArrayAccess. You have to implement it to your class.
class MyOwnClass implements ArrayAccess {
private $arr = null;
public function __construct($arr = null) {
if(is_array($arr))
$this->arr = $arr;
else
$this->arr = [];
}
public function offsetExists ($offset) {
if($this->arr !== null && isset($this->arr[$offset]))
return true;
return false;
}
public function offsetGet ($offset) {
if($this->arr !== null && isset($this->arr[$offset]))
return $this->arr[$offset];
return false;
}
public function offsetSet ($offset, $value) {
$this->arr[$offset] = $value;
}
public function offsetUnset ($offset) {
unset($this->arr[$offset]);
}
}
Use:
$arr = ["a" => 20, "b" => 30];
$obj = new MyOwnClass($arr);
$obj->offsetGet("a"); // Gives 20
$obj->offsetSet("b", 10);
$obj->offsetGet("b"); // Gives 10

Related

Make modification to arguments before a method gets called

What would be the best way to perform some modification on a method argument before the method is actually executed?
I have tried achieving this with a combination of attributes and use of the decorator/proxy pattern:
#[\Attribute]
class Query
{
}
class Foo
{
#[Query]
public function request(array $query = [])
{
}
public function foo(array $query = [])
{
}
#[Query]
public function bar(string $name, int $age, array $query = [])
{
}
}
class FooDecorator
{
private Foo $target;
public function __construct(Foo $target)
{
$this->target = $target;
}
public function __call(string $methodName, array $args)
{
$class = get_class($this->target);
try {
$reflection = new \ReflectionClass($class);
$methods = $reflection->getMethods();
$attributeName = __NAMESPACE__ . '\Query';
foreach ($methods as $method) {
if ($method->getName() !== $methodName) {
continue;
}
$attributes = $method->getAttributes();
foreach ($attributes as $attribute) {
if ($attribute->getName() === $attributeName) {
$parameters = $method->getParameters();
foreach ($parameters as $key => $param) {
if ($param->getName() === 'query') {
// Here we filter the parameter named 'query'
$args[$key] = $this->validateQueryParameter($args[$key]);
break;
}
}
}
}
}
} catch (\Exception $e) {
}
if (method_exists($this->target, $methodName)) {
return call_user_func_array([$this->target, $methodName], $args);
}
}
private function validateQueryParameter(array $query): array
{
$allowed = [
'foo',
'bar',
];
$query = array_filter($query = array_change_key_case($query), function ($key) use ($allowed) {
// Filter out any non-allowed keys
return in_array($key, $allowed);
}, ARRAY_FILTER_USE_KEY);
return $query;
}
}
$foo = new FooDecorator(new Foo());
// Should remove faz & baz, but keep foo in the query
$foo->query(['faz' => 1, 'baz' => 2, 'foo' => 3]);
// Should not perform anything on the query parameter
$foo->foo(['baz' => 1]);
// Should remove faz & baz, but keep foo in the query
$foo->bar('foo', 100, ['faz' => 1, 'baz' => 2, 'foo' => 3]);
This works as expected, but since I am using a decorator here I am now missing the hints for each method included in the Foo class.
I know that I could use an interface and declare all methods included in Foo, but then I would need to implement every method (the real class contains many many methods) in the decorator, which seems like overkill.

Is it possible to make a unique value in PHP?

I'm making a array class and want a value to be able to be returned by a higher order function. The idea is that its a instance constant or method returned value such that I can skip the value in a map.
In other languages making an array or some compound value, like ['skip'] will make it pointer equal such that I can then use the operator for pointer equal and it will not be equal to other arrays with the exact same content, but my problem is that ['skip'] === ['skip'] is true so even with === the two values are the same.
Here is an example of usage of my code where I accedentally have the same value as I used to skip:
namespace Test;
use Common\Domain\Collection;
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? Collection::SKIP : ["skip"];
});
echo count($arr2); // prints 0, but should be 2
Is there a way to get a unique value or work around this somehow?
Here is code that implements Collection:
namespace Common\Domain;;
class Collection implements \Iterator, \Countable, \ArrayAccess
{
const SKIP = ["skip"];
private $arr = [];
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result !== self::SKIP) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
// implementation of interfaces \Iterator, \Countable, \ArrayAccess
public function current()
{
return current($this->arr);
}
public function next()
{
next($this->arr);
}
public function key()
{
return key($this->arr);
}
public function valid()
{
return isset($this->arr[$this->key()]);
}
public function rewind()
{
reset($this->arr);
}
public function count()
{
return count($this->arr);
}
public function offsetExists($offset)
{
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset)
{
return $this->arr[$offset];
}
public function offsetSet($offset, $value)
{
$this->arr[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->arr[$offset]);
}
}
I guess you are looking for Java-type enumerations, which doesn't exist in PHP. My best guess on your problem would be to use an object instead of a constant, that you would instantiate statically for a convenient use. Then, in the loop of your map function, you check the value with an instanceof instead of the basic equality operator, against the class you defined.
So, here :
class UniqueValue
{
public static function get()
{
return new self();
}
}
Then :
$arr2 = $arr->map(function ($v) {
return $v % 2 == 0 ? UniqueValue::get() : ["skip"];
});
And inside your collection :
public function map(callable $fn, bool $keepKeys = false) :Collection
{
$arr = new static();
$nOrder = 0;
foreach($this->arr as $key => $value) {
$result = call_user_func($fn, $value, $key, $nOrder, $this);
if($result ! instanceof UniqueValue) {
if($keepKeys) {
$arr[$key] = $result;
} else {
$arr[] = $result;
}
}
}
return $arr;
}
This is the quickest approach I can think of. If your array contains data from "outside" I don't think it's possible in any way that it matches against a class check from your own code.
I would solve this by implementing another method for this. The method delete would map a function over the collection and remove any elements where the function returns false.
e.g.
class Collection
{
// ...
public function delete($func)
{
$result = new static();
foreach($this->arr as $item)
{
if($func($item) !== false) $result[] = $item;
}
}
}
// example
$arr = new Collection();
$arr[] = 1;
$arr[] = 2;
$arr[] = 3;
$arr[] = 4;
echo count($arr); // prints 4
$arr2 = $arr->delete(function ($v) {
return $v % 2 ? true : false;
});
var_dump($arr2); // prints [2, 4]

Conditionally chain a method?

How do we conditionally chain methods in PHP? For example, this works fine:
$a->foo()->bar->baz->qux();
However, depending on a condition, I'd like to chain some methods but not others. Basically, shorten the following code:
if ($cond === true) {
$a->foo()->baz();
} else {
$a->foo()->bar();
}
Ideally something like the following would work:
$a->foo()
->bar()
($cond === true) ? ->baz() : ->qux()
->more();
Additionally, how would we conditionally chain a method (or not) depending on a condition? For example:
$a->foo()
->bar()
if($cond === true) ->baz()
->more();
The Self-Explanatory Mock-Snippet below (which you may Quick-Test Here) shows how you could do that
<?php
class Test{
protected $prop1;
protected $prop2;
protected $prop3;
protected $prop4;
public function __construct() {
}
public function setProp1($prop1) {
$this->prop1 = $prop1;
return $this;
}
public function setProp2($prop2) {
$this->prop2 = $prop2;
return $this;
}
public function setProp3($prop3) {
$this->prop3 = $prop3;
return $this;
}
public function setProp4($prop4) {
$this->prop3 = $prop4;
return $this;
}
}
$a = 2;
$b = 7;
$cond = ($a > $b);
$cond2 = ($b > 50);
$test = new Test;
$test->setProp1(2)->{($cond === true) ? 'setProp4' : 'setProp3'}(11);
$test->setProp3(3)->{($cond2 === false) ? 'setProp2' : 'setProp4'}(6);
var_dump($test);
//YIELDS::
object(Test)[1]
protected 'prop1' => int 2
protected 'prop2' => int 6
protected 'prop3' => int 3
protected 'prop4' => null
What you're looking for is variable methods (see example #2). They allow you to do something like this:
class a {
function foo() { echo '1'; return $this; }
function bar() { echo '2'; return $this; }
function baz() { echo '3'; return $this; }
}
$a = new a();
$cond = true;
$a->foo()->{($cond === true) ? 'baz' : 'bar'}();
// Prints 13
$cond = false;
$a->foo()->{($cond === true) ? 'baz' : 'bar'}();
// Prints 12
Here's a way that lets you set up requirements for each of the function calls. Note that this is just as hard to maintain as the previous solution, if not harder. You'll probably want to use some sort of configuration and the ReflectionClass's getMethods function, too.
class a {
function foo() { echo '1'; return $this; }
function bar() { echo '2'; return $this; }
function baz() { echo '3'; return $this; }
}
function evaluateFunctionRequirements($object, $functionRequirements, $condition) {
foreach ($functionRequirements as $function=>$requirements) {
foreach ($requirements as $requiredVariableName=>$requiredValue) {
if (${$requiredVariableName} !== $requiredValue) {
continue 2;
}
}
$object->{$function}();
}
}
$a = new a();
$functionRequirements = array('foo'=>array(), 'bar'=>array(), 'baz'=>array('condition'=>true));
$condition = true;
evaluateFunctionRequirements($a, $functionRequirements, $condition);
// Prints 123
$condition = false;
evaluateFunctionRequirements($a, $functionRequirements, $condition);
// Prints 12
Notes: This has the added even harder to maintain of requiring the functions in order for the $functionRequirements array. Additionally, this rudimentary example has only one possible condition var passed, update to another setup for getting more $requiredVariableName vars with func_get_args. You'll also want to verify that the methods passed in via $functionRequirements are is_callable() safe.
Try this by assigning the chaining to variable
$a = $a->foo();
if ($cond === true) {
$a = $a->baz();
} else {
$a = $a->bar();
}
$a->more();
Another way to solve this is to create a method when (or name it whatever makes sense to you):
public function when($condition, $callback)
{
if ($condition) {
return $callback($this) ?: $this;
}
return $this;
}
Of course, you can extend it to accept additional arguments if you need to pass them to your methods foo, bar, etc...
And the usage with chaining would be:
$a->when($cond === true, function ($a) {
return $a->foo();
})->when($cond !== true, function ($a) {
return $a->bar();
}
)->baz(); // a regular chaining method without condition

How to use __get() to return null in multilevel object property accessing?

How can I use __get() to return null in multilevel object property accessing the case like this below?
For instance, this is my classes,
class property
{
public function __get($name)
{
return (isset($this->$name)) ? $this->$name : null;
}
}
class objectify
{
public function array_to_object($array = array(), $property_overloading = false)
{
# if $array is not an array, let's make it array with one value of former $array.
if (!is_array($array)) $array = array($array);
# Use property overloading to handle inaccessible properties, if overloading is set to be true.
# Else use std object.
if($property_overloading === true) $object = new property();
else $object = new stdClass();
foreach($array as $key => $value)
{
$key = (string) $key ;
$object->$key = is_array($value) ? self::array_to_object($value, $property_overloading) : $value;
}
return $object;
}
}
How I use it,
$object = new objectify();
$type = array(
"category" => "admin",
"person" => "unique",
"a" => array(
"aa" => "xx",
"bb"=> "yy"
),
"passcode" => false
);
$type = $object->array_to_object($type,true);
var_dump($type->a->cc);
result,
null
but I get an error message with NULL when the input array is null,
$type = null;
$type = $object->array_to_object($type,true);
var_dump($type->a->cc);
result,
Notice: Trying to get property of non-object in C:\wamp\www\test...p on line 68
NULL
Is it possible to return NULL in this kind of scenario?
Yes it is, but it's not so trivial to explain how. First understand why you run into that problem:
$value = $a->b->c;
This will first return NULL for $a->b. So actually you wrote:
$value = NULL->c;
So instead of NULL on an unset item you need to return a NULL-object (let's namne it NULLObect) that works similar to your base class and that represents NULL.
As you can imagine, you can not really simulate NULL with it in PHP and PHP's language features are too limited to make this seamlessly.
However you can try to come close with the NULLObect I've describben.
class property
{
public function __get($name)
{
isset($this->$name) || $this->$name = new NULLObject();
return $this->$name;
}
}
class NULLObject extends Property {};
Take care that this might not be exactly what you're looking for. If it does not matches your need, it's highjly likely that PHP is the wrong language you use for programming. Use some language that has better features of member overriding than PHP has.
Related Question:
Working with __get() by reference
You can can return new property instead of null
public function __get($name)
{
return (isset($this->$name)) ? $this->$name : new property();
}
Yes, I know it's been 4 years ago, but I had a similar problem this week, and while I trying to solve it, I found this thread. So, here is my solution:
class NoneWrapper
{
private $data;
public function __construct($object)
{
$this->data = $object;
}
public function __get(string $name)
{
return property_exists($this->data, $name)
? new NoneWrapper($this->data->$name)
: new None;
}
public function __call($name, $arguments)
{
if (is_object($this->data)) {
return (property_exists($this->data, $name))
? $this->data->$name
: null;
} else {
return null;
}
}
public function __invoke()
{
return $this->data;
}
}
class None
{
public function __get(string $name) {
return new None;
}
public function __call($name, $arguments)
{
return null;
}
public function __invoke()
{
return null;
}
}
$object = new NoneWrapper(
json_decode(
json_encode([
'foo' => [
'bar' => [
'first' => 1,
'second' => 2,
'third' => 3,
'fourth' => 4,
],
]
])
)
);
var_dump($object->foo->bar->baz()); // null
var_dump($object->foo->bar->third()); // 3

How do I fill an object in PHP from an Array

Suppose I have:
class A{
public $one;
public $two;
}
and an array with values:
array('one' => 234, 'two' => 2)
is there a way to have an instance of A filled with the right values from the array automatically?
You need to write yourself a function for that. PHP has get_object_varsDocs but no set counterpart:
function set_object_vars($object, array $vars) {
$has = get_object_vars($object);
foreach ($has as $name => $oldValue) {
$object->$name = isset($vars[$name]) ? $vars[$name] : NULL;
}
}
Usage:
$a = new A();
$vars = array('one' => 234, 'two' => 2);
set_object_vars($a, $vars);
If you want to allow for bulk-setting of attributes, you can also store them as a property. It allows you to encapsulate within the class a little better.
class A{
protected $attributes = array();
function setAttributes($attributes){
$this->attributes = $attributes;
}
public function __get($key){
return $this->attributes[$key];
}
}
#hakre version is quite good, but dangerous (suppose an id or password is in thoses props).
I would change the default behavior to that:
function set_object_vars($object, array $vars) {
$has = get_object_vars($object);
foreach ($has as $name => $oldValue) {
array_key_exists($name, $vars) ? $object->$name =$vars[$name] : NULL;
}
}
here, the previous properties that are not in the $vars array are not affected.
and if you want to set a prop to null on purpose, you can.
Yes there is.
You could use a pass thru method.
For example:
class A {
public $one, $tow;
function __construct($values) {
$this->one = $values['one'] ?: null;
$this->two = $values['two'] ?: null;
}
}
$a = new A(array('one' => 234, 'two' => 2));

Categories