In a PHP program I have an array of some custom objects, and I want to find if the array contains a certain object. Of course I can use array_search, but this checks if the objects are the same object, not if it has the same variables. So I want to be able to create my own compare function for the objects, which I can use with the array_search method (or something similar).
I want to be able to do something like this:
class foo
{
public $_a,$_b;
function __construct($a,$b)
{
$this->_a = $a;
$this->_b = $b;
}
function __equals($object)
{
return $this->_a == $object->_a;
}
}
$f1 = new foo(5,4);
$f2 = new foo(4,6);
$f3 = new foo(4,5);
$array = array($f1,$f2);
$idx = array_search($f3,$array); // return 0
Is something like this possible?
I know I can also create my own array_search method which uses a method from the class, but than I'd have to use 2 different search functions, one for the classes which do have their own compare function, and one for those which haven't.
Here's a neat little trick I recently found out:
class Foo {
public $a;
public $b;
public function __toString() {
return (string)$this->a;
}
public function __construct($a, $b) {
$this->a = $a;
$this->b = $b;
}
}
$a = new Foo(1, 'a');
$b = new Foo(2, 'b');
$c = new Foo(3, 'c');
$d = new Foo(2, 'd');
$array = array($a, $b);
$key = array_search($d, $array); // false
$key = array_search((string)$c, $array); // false
$key = array_search((string)$d, $array); // 1
This also works:
$is_equal = ((string)$d == $b); // true
When passed a string $needle, array_search will try to cast the objects contained in $haystack to string to compare them, by calling the __toString magic method if it exists, which in this case returns Foo::$a.
Usually its not. You may look at the PECL Operators-Extension, but thats really old.
Related
I want to compare an array of complex objects with an array of ids, with the expected result being an array of any object that did not have its id listed.
This sounds like a perfect use case for array_udiff, but I couldn't get it work without some confusing hassle. To illustrate my problem with that function, here a boiled down example:
class Foo {
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$foos = [new Foo(1), new Foo(2), new Foo(3), new Foo(4)];
$fooIds = [1, 2, 3, 4];
$diff = array_udiff($foos, $fooIds, function ($f, $i){ return $f->id - $i; });
print_r($diff); // expected result: an empty array
// actual result:
// Array
// (
// [1] => Foo Object
// (
// [id] => 2
// )
// )
// Object of class Foo could not be converted to int :11
It sounds to me like array_udiff tries to do some type coercion between the elements of the arrays. I found no mention of this in the docs, and one question on SO seems to ask something similar, but doesn't have any answers. What I'd like to know:
Why does array_udiff behave this way? If we can supply an arbitrary callback function, this kind of coercion seems completely unnecessary, and in my case even very unhelpful.
Is there a good way to work around this, or should I use a different of function given my general problem?
Though a bit ugly, the seemingly simplest way to get the required result is to type-check before comparing.
<?php
declare(strict_types=1);
error_reporting(-1);
ini_set('display_errors', 'On');
class Foo
{
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$foos = [new Foo(1), new Foo(2), new Foo(3), new Foo(4)];
$fooIds = [1, 2, 3, 4];
$diff = array_udiff($foos, $fooIds, function ($a, $b) {
echo gettype($a), ' <=> ', gettype($b), "\n";
if ($a instanceof Foo && $b instanceof Foo) {
return $a->id <=> $b->id;
}
if ($a instanceof Foo) {
return $a->id <=> $b;
}
if ($b instanceof Foo) {
return $a <=> $b->id;
}
return $a <=> $b;
});
print_r($diff); // expected result: an empty array
demo: https://3v4l.org/1uVYf
I think #apokryfos (from the answer you shared):
It makes sense that it would not have $a from the first array and $b
from the second array. What you essentially have in the function is a
comparator so if you sort both arrays based on the comparator (in O(nlogn) time) you can then get the diff in O(n) time in a sort-join
manner. If it was just pairwise comparisons it would be O(n^2) so I
suggest you treat your callback as the general comparator function
which works for the combined first and second array
Any way I recommend that simple workaround using array_column and the star-ship operator:
$diff = array_udiff(array_column($foos, "id"), $fooIds, function ($f, $i){ return $f <=> $i; });
array_column works on object too from PHP 7
Use an intermediate function that specializes in taking the comparison value of either Foo ($f->id) or non-Foo ($f):
$val = function($f) {
return $f instanceof Foo ? $f->id : $f;
};
$diff = array_udiff(
$foos,
$fooIds,
function ($f, $i) use ($val) { return $val($f) - $val($i); }
);
Or in a single comparison function:
function ($f, $i) { return ($f instanceof Foo ? $f->id : $f) - ($i instanceof Foo ? $i->id ? $i); }
I have 3 function, it is a() b() and c()
and then, function a() returned :
{
id =>
total1 =>
}
function b() returned like funcion a() too, but have a different value (total1 and total2)
function c() returned :
{
id => //same value just like function a or b
name =>
}
$a = $model->a(); // from function a();
$b = $model->b(); // from function b();
$c = $model->c(); // from function c();
i want combine that 3 returned values into array like this :
array = ['id','name', 'total1', 'total2']
any idea ? Thanks
You can use array_merge() and array_keys():
array_keys(array_merge($a, $b, $c));
If variables are not arrays, convert them to arrays with toArray() or json_decode() first.
If functions return Laravel collections, you can use merge() and keys() helpers.
You can cast the results to array
$a = $model->a()->toArray(); // from function a();
$b = $model->b()->toArray(); // from function b();
$c = $model->c()->toArray(); // from function c();
and then
$data= array_merge($a , $b, $c)
How can i access either a single or entire multiple return from a function inside a class?
Normally from a function, i can access multiple returns like
list($name, $number ) = functionFromSomeClass($a, $b, $c);
$n1 = $name;
$n2 = $number;
but when this function is inside a class, i seem to be having problems access the entire or single return
class MyClass{
public function newFunc( $var_1, $var_2 ) {
require_once('someOtherClass.php');
// process
list($name, $number ) = functionFromSomeOtherClass($a, $b, $c);
}
}
then to access:
$myclass = new MyClass;
$name = $myclass->newFunc($q, $w)->$name;
or, how would i get the entire list return?
$myclass = new MyClass;
list($name, $number) = $myclass->newFunc($q, $w);
If you want to access to another function in a class you should:
$this->functionFromSomeOtherClass($a, $b, $c);
In other word:
class MyClass
{
public function newFunc( $var_1, $var_2 ) {
require_once('someOtherClass.php');
// process
$someotherclass = new someOtherClass();
list($name, $number ) = $someotherclass->functionFromSomeOtherClass($a, $b, $c);
}
}
When you use the syntax
list($var1, $var2, ...) = <expression>;
it just means that <expression> must evaluate to an array, and the variables will be assigned from successive elements of the array. <expression> can be any calculation, including function or method calls; you write it the same way as you would if it were being assigned normally. But if it's a function or method call, the function/method must return an array for this to be valid.
There's nothing special about using this when calling functions from another class.
You'll need to create an instance of the other class, or call it as a static function, depending on how the method is declared:
someOtherClass.php:
class OtherClass
{
public function functionFromSomeOtherClass($a, $b, $c)
{
return $a + $b + $c;
}
}
MyClass.php:
class MyClass{
public function newFunc( $var_1, $var_2 ) {
require_once('someOtherClass.php');
// Instance call:
$otherClass = new OtherClass();
list($name, $number ) = $otherClass->functionFromSomeOtherClass($a, $b, $c);
// Static call:
list($name, $number ) = OtherClass::functionFromSomeOtherClass($a, $b, $c);
}
}
If I understand what you're saying, you are looking to pass the values of some other class and then pass them on in a different location when you call a function. You can do this in a few ways. One way, you'd be returning values in the function. Another way, you can declare public class variables like so:
class MyClass{
public $name,$number;
public function newFunc( $var_1, $var_2 ) {
require_once('someOtherClass.php');
// process
list($this->name, $this->number ) = functionFromSomeOtherClass($a, $b, $c);
}
}
That way when you're calling a function like so:
$myClass = new MyClass;
You can simply call a function and access variables here:
$myClass->newFunc($var1,$var2);
$n1 = $myClass->name;
$n2 = $myClass->number;
Again, the other option is just to return the results of the other class, like so:
class MyClass{
public $name,$number;
public function newFunc( $var_1, $var_2 ) {
require_once('someOtherClass.php');
// process
$otherClass = new OtherClass;
return $otherCLass->functionFromSomeOtherClass($a, $b, $c);
}
}
$myClass = new MyClass;
list($name,$number) = $myClass->newFunc($var1,$var2);
If that's not what you're getting at, as I suggested in a comment, it would help to have a more detailed example.
You are not even returning.
function get_multi() {
return array(1, 2, 3);
}
list($a, $b, $c) = get_multi();
class Test {
private $arr;
function __construct() {
$this->arr = array('test');
}
function getArr() {
return $this->arr;
}
}
$a = new Test();
$b = $a->getArr();
$b[0][0] = 'a';
$s = $a->getArr();
echo $s[0]
Why does this echo test instead of aest? Does PHP copy the array and the contents of the array when returning it? How do I get an array in which I can change the strings and have it reflected in the object?
By returning and assigning by reference:
class Test {
//...
function &getArr() {
return $this->arr;
}
}
$a = new Test();
$b =& $a->getArr();
$b[0][0] = 'a';
$s = $a->getArr();
echo $s[0];
Does PHP copy the array and the contents of the array when returning it?
From the point of view of the programmer, it works as if returning would copy the value, except when returning by reference. In terms of implementation, there are optimizations that avoid this happens, as long as it has no impact in the behavior of the script.
Is there a way to create a PHP array which always treated by reference without having to use the & operator?
For instance:
$a = array_by_ref('a', 'b', 'c');
$b = $a;
$b[] = 'd';
should result in both $a and $b being equal to:
('a', 'b', 'c', 'd')
If SPL is available, there is the ArrayObject class:
$a = new ArrayObject(array('a', 'b', 'c'));
$b = $a;
$b[] = 'd';
These are still wrapper objects though; to get their primitive array equivalents you have to use the object's getArrayCopy() method. Also bear in mind that it can be quite slow, particularly when you iterate through its elements.
ArrayObject doesn't go along with array_map, array_reduce, and similar functions that expect a real array as an input. If you want an array property of an object to be copied by reference, wrap it with any kind of object:
class Test
{
private $array;
public function __construct()
{
$this->array = (object) ['array' => []];
}
// we also need to return it by reference
public function &getMyArray()
{
return $this->array->array;
}
}
Sample usage:
$test = new Test();
$test->getMyArray()[] = 'Hello';
$another = clone $test;
$another->getMyArray()[] = 'Fucking';
$third = clone $another;
$third->getMyArray()[] = 'World!';
unset($test->getMyArray()[1]);
var_dump($test->getMyArray() === $third->getMyArray());
var_dump(implode(" ", $test->getMyArray()));
var_dump(gettype($test->getMyArray()));
Sample output:
bool(true)
string(12) "Hello World!"
string(5) "array"