So, how do you compare classes that contain Closure ? It looks like you can't.
class a {
protected $whatever;
function __construct() {
$this->whatever = function() {};
}
}
$b = new a();
$c = new a();
var_dump( $b == $c ); //false
Well you can't serialize() closures straight on, but you can do a workaround, since serialize() invokes __sleep() when it's serializing objects, so it gives the object to option to clean things up! This what we're doing here:
class a {
protected $whatever;
function __construct() {
$this->whatever = function() {};
}
public function __sleep() {
$r = [];
foreach ($this as $k => $v){
if (!is_array($v) && !is_string($v) && is_callable($v))
continue;
$r[] = $k;
}
return $r;
}
}
So now you can use serialize() with md5() to compare your objects like this:
var_dump(md5(serialize($b)) === md5(serialize($c)));
output:
bool(true)
Related
Is it possible to pass an anonymous function as a parameter in PHP? And if yes - how?
I am trying to pass an anonymous function to a setter which will fill an array with values returned from that function.
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', function(){return 123;});
}
private function setArrElm($name, $val)
{
// here: gettype($val) == object
$this->arr[$name] = $val;
}
}
Please note the comment - the type of val is object and I expect an int.
In PHP 7 you can self execute the closure
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', (function(){return 123;})()); //<-- self execute
}
private function setArrElm($name, int $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Output
123
Sandbox
This takes a form similar to JS (probably other languages too):
(function(){return 123;})()
It's important to know that it's executing the function, then passing the result. You can pass the closure (which is an object) and then execute it, too. But if you have strict types and need an int, you can self execute the closure too.
It really only makes sense to do this if you need an int as the argument. Even in that case you can execute it beforehand and then pass the result. This just saves you a local variable.
For < PHP7 or just because
Alt1
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var()); //<-- execute
}
private function setArrElm($name, $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt2
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(gettype($val) == 'object' && is_a($val, '\Closure')){
//is a closure, you could use is_callable etc. too. see __invoke()
$val = $val();
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt3
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(is_callable($val)){
//pass functions (as a string) or arrays or closures(executable classes with __invoke)
$val = call_user_func($val);
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Cheers
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]
I have a class that is basically a wrapper to an Array and implements IteratorAggregate.
When a new object of the class is created, it stores it's value in a protected variable called $value. The value can be a string, integer, etc.. or a traversable object (like an array).
This object is "recursive" since when a traversable object (like an array) is passed to the constructor, he creates another instance of the class. Here's the constructor to clarify:
class ListItem implements \IteratorAggregate
{
protected $readOnly = false;
protected $key;
protected $value;
public function __construct($key, $value, $readOnly = false)
{
if($readOnly) $this->readOnly = true;
if(is_numeric($key)) $key = 'index'.$key;
$this->key = $key;
if (is_array($value) || $value instanceof Traversable) {
$this->value = array();
foreach($value as $k => $v) {
if(is_numeric($k)) $k = 'index'.$k;
$this->value[$k] = new ListItem($k, $v, $readOnly);
}
} else {
$this->value = $value;
}
}
public function __toString()
{
if ( is_array($this->value) ) {
return 'ListItem OBJECT(' . count($this->value) . ')';
} else {
return $this->value;
}
}
Right now, I'm trying to write a simple sorting method for the class.
To sort by Key, this works like a charm:
$success = ksort($this->value, $comparison);
but to sort by value, asort does not work since the actual value I'm trying to sort is stored inside the value property.
So I tried using uasort, like this:
$success = uasort($this->value, function ($a, $b)
{
if ($a->value == $b->value) return 0;
else if($a->value < $b->value) return -1;
else return 1;
});
but for some unclear reason i get the following error:
Warning: uasort() [function.uasort]: Array was modified by the user
comparison function in /* /* /* /ListItem.php on line 129
Q. Why does this happen if I'm just accessing $value for comparison not actually changing anything?
It seems a closure (or anonymous function) is in the global scope which means uasort could not access private or protected members of the ListItem object (although other ListItem Objects can access their sibling's private/protected properties)
this solved the problem: (casting to string)
$success = uasort($this->value, function ($objA, $objB) use ($comparison)
{
$a = (string) $objA;
$b = (string) $objB;
if($comparison == ListItem::SORT_NUMERIC) {
if (is_numeric($a)) $a = (int) $a;
if (is_numeric($b)) $b = (int) $b;
}
if ($a == $b) return 0;
else if($a < $b) return -1;
else return 1;
});
One of my dreams is to use python rich comparison (something like __eq__) on php objects.
class A {
public $a = 1;
public function __eq__($other) {
return $this->a == $other->a;
}
}
class B {
public $a = 2;
public function __eq__($other) {
return $this->a == $other->a;
}
}
class C {
public $a = 1;
public function __eq__($other) {
return $this->a == $other->a;
}
}
$a = new A();
$b = new B();
$c = new C();
echo $a == $b; //false
echo $a == $c; //true
I'd like to have some smart mechanism to fast-compare models (objects) on database id, for example.
Is it possible in some way in PHP?
No, it isn't. A common way to achieve this is to use an equals() method, but there isn't any magic method. You'll have to call it manually. For example:
<?php
class User
{
private $id;
public function __construct($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function equals(User $user)
{
return $this->getId() === $user->getId();
}
}
$user1 = new User(1);
$user2 = new User(2);
var_dump($user1->equals($user2)); // bool(false)
var_dump($user2->equals($user1)); // bool(false)
?>
Which, I think, isn't much different from:
var_dump($user1 == $user2);
var_dump($user2 == $user1);
Anyway, my example will work even using the == operator, since it will compare the values of all the properties.
In effect, if I have a class c and instances of $c1 and $c2
which might have different private variable amounts but all their public methods return the same values I would like to be able to check that $c1 == $c2?
Does anyone know an easy way to do this?
You can also implement a equal($other) function like
<?php
class Foo {
public function equals($o) {
return ($o instanceof 'Foo') && $o.firstName()==$this.firstName();
}
}
or use foreach to iterate over the public properties (this behaviour might be overwritten) of one object and compare them to the other object's properties.
<?php
function equalsInSomeWay($a, $b) {
if ( !($b instanceof $a) ) {
return false;
}
foreach($a as $name=>$value) {
if ( !isset($b->$name) || $b->$name!=$value ) {
return false;
}
}
return true;
}
(untested)
or (more or less) the same using the Reflection classes, see http://php.net/manual/en/language.oop5.reflection.php#language.oop5.reflection.reflectionobject
With reflection you might also implement a more duck-typing kind of comparision, if you want to, like "I don't care if it's an instance of or the same class as long as it has the same public methods and they return the 'same' values"
it really depends on how you define "equal".
It's difficult to follow exactly what you're after. Your question seems to imply that these public methods don't require arguments, or that if they did they would be the same arguments.
You could probably get quite far using the inbuilt reflection classes.
Pasted below is a quick test I knocked up to compare the returns of all the public methods of two classes and ensure they were they same. You could easily modify it to ignore non matching public methods (i.e. only check for equality on public methods in class2 which exist in class1). Giving a set of arguments to pass in would be trickier - but could be done with an array of methods names / arguments to call against each class.
Anyway, this may have some bits in it which could be of use to you.
$class1 = new Class1();
$class2 = new Class2();
$class3 = new Class3();
$class4 = new Class4();
$class5 = new Class5();
echo ClassChecker::samePublicMethods($class1,$class2); //should be true
echo ClassChecker::samePublicMethods($class1,$class3); //should be false - different values
echo ClassChecker::samePublicMethods($class1,$class4); //should be false -- class3 contains extra public methods
echo ClassChecker::samePublicMethods($class1,$class5); //should be true -- class5 contains extra private methods
class ClassChecker {
public static function samePublicMethods($class1, $class2) {
$class1methods = array();
$r = new ReflectionClass($class1);
$methods = $r->getMethods();
foreach($methods as $m) {
if ($m->isPublic()) {
#$result = call_user_method($m->getName(), $class1);
$class1methods[$m->getName()] = $result;
}
}
$r = new ReflectionClass($class2);
$methods = $r->getMethods();
foreach($methods as $m) {
//only comparing public methods
if ($m->isPublic()) {
//public method doesn't match method in class1 so return false
if(!isset($class1methods[$m->getName()])) {
return false;
}
//public method of same name doesn't return same value so return false
#$result = call_user_method($m->getName(), $class2);
if ($class1methods[$m->getName()] !== $result) {
return false;
}
}
}
return true;
}
}
class Class1 {
private $b = 'bbb';
public function one() {
return 999;
}
public function two() {
return "bendy";
}
}
class Class2 {
private $a = 'aaa';
public function one() {
return 999;
}
public function two() {
return "bendy";
}
}
class Class3 {
private $c = 'ccc';
public function one() {
return 222;
}
public function two() {
return "bendy";
}
}
class Class4 {
public function one() {
return 999;
}
public function two() {
return "bendy";
}
public function three() {
return true;
}
}
class Class5 {
public function one() {
return 999;
}
public function two() {
return "bendy";
}
private function three() {
return true;
}
}
You can define PHP's __toString magic method inside your class.
For example
class cat {
private $name;
public function __contruct($catname) {
$this->name = $catname;
}
public function __toString() {
return "My name is " . $this->name . "\n";
}
}
$max = new cat('max');
$toby = new cat('toby');
print $max; // echoes 'My name is max'
print $toby; // echoes 'My name is toby'
if($max == $toby) {
echo 'Woohoo!\n';
} else {
echo 'Doh!\n';
}
Then you can use the equality operator to check if both instances are equal or not.
HTH,
Rushi
George: You may have already seen this but it may help: http://usphp.com/manual/en/language.oop5.object-comparison.php
When using the comparison operator (==), object variables are compared in a simple manner, namely: Two object instances are equal if they have the same attributes and values, and are instances of the same class.
They don't get implicitly converted to strings.
If you want todo comparison, you will end up modifying your classes. You can also write some method of your own todo comparison using getters & setters
You can try writing a class of your own to plugin and write methods that do comparison based on what you define. For example:
class Validate {
public function validateName($c1, $c2) {
if($c1->FirstName == "foo" && $c2->LastName == "foo") {
return true;
} else if (// someother condition) {
return // someval;
} else {
return false;
}
}
public function validatePhoneNumber($c1, $c2) {
// some code
}
}
This will probably be the only way where you wont have to modify the pre-existing class code