Comparing 2 objects PHP - php

i need to compare 2 objects to remove duplicates / find new enteries.
The objects are not identical, but they contain the same username key
Here is the layout
database object
array
[0]db->username
[0]db->something
[1]db->username
[1]db->something
etc
other object
array
[0]ob->username
[0]ob->somethingElse
[1]ob->username
[1]ob->somethingElse
etc
I imagine i can loop one array of objects, and compare the $db[$key]->username with an internal loop of the other object $ob[$key]->username but is there a cleaner way ?
I am looking to remove duplicates

No, there is no cleaner way, you have to loop over the properties. If that are not StdClass objects, I would add a custom compare method to their class:
class Person {
protected $id;
protected $name;
protected $age;
/**
* Compares two persons an returns true if their name
* and age equals.
*/
public function equals(Person $b) {
if($b->name === $this->name && $b->age === $this->age) {
return TRUE;
}
return FALSE;
}
}
Then use it like this:
$personA = DB::getPersonById(1);
$personB = DB::getPersonById(2);
if($personA->equals($personB)) {
echo "They are equal";
}
However, beside from this, why not just removing the duplicates using SQL or even better use unique keys in the DB to avoid duplicates at all?

Related

PHP: How to get single value from array that was returned from a class?

class Test {
public function results() {
$return['first'] = 'one';
$return['second'] = 'two';
return $return;
}
}
$test = new Test;
print_r($test->results()); // Returns entire array
I just want to return a single specified element from the array, such as the value of key "second". How do I do this without sifting through the entire array after it's returned?
I just want to return a single specified element from the array, such as the value of key "second"
Pass in an argument to identify which element to return, and return that (or false if it doesn't exist - for example);
public function results($key = null)
{
$return['first'] = 'one';
$return['second'] = 'two';
// check the key exists
if (!array_key_exists($key, $return)) {
return false;
}
return $return[$key];
}
Then:
print_r($test->results('second')); // two
How do I do this without sifting through the entire array after it's returned?
It's important to note that you do not need to "sift through the entire array" to retrieve a value by its key. You know the key, so you can access it directly.
class Test {
private $arr; //private property of object
__construct(){
//create arr in constructor
$this->arr=[];//create new array
$this->arr['first'] = 'one';
$this->arr['second'] = 'two';
}
/**
/* get array
**/
public function getResults(){
return $this->arr;
}
/**
/* get single array element
**/
public function getResult($key) {
return isset($this->arr[$key])?$this->arr[$key]:null;//return element on $key or null if no result
}
}
$test = new Test();
print_r($test->getResult("second")); // Returns array element
//or second possibility but the same result
print_r($test->getResults()["second"]); // Returns array element
Few advices:
Create data structure in constructor ($arr in this particular case) because creating it on very results method call is not any kind of using objects or objective programming. Imagine that if array is created in results method then on every call new array is located in memory, this is not efficent, not optimal and gives no possibility to modify this array inside class Test.
Next in method results add parameter to get only this key what is needed and hide all array in private class property $arr to encapsulate it in object.
And last my private opinion for naming style:
Use camelCase when naming method names.
In PHP an array value can be dereferenced from the array by its key.
$arr = ["foo" => "bar", "baz" => "quix"];
echo $arr["foo"]; // gives us "bar"
echo $arr["baz"]; // gives us "quix"
If the method/function returns an array the same can be done with the return value, whether by assigning the return value to a variable and using the variable to dereference the value by key, or by using function array dereferencing.
class Test {
public function results() {
return ["foo" => "bar", "baz" => "quix"];
}
}
$test = new Test;
$arr = $test->results();
echo $arr["foo"]; // gives us "bar"
echo $arr["baz"]; // gives us "quix"
// Using FAD (Function Array Dereferencing)
echo $test->results()["foo"]; // gives us "bar"
echo $test->results()["baz"]; // gives us "quix"
Of course there are two important caveats to using function array dereferencing.
The function is executed each time you do it (i.e no memoziation)
If the key does not exist or the function returns something other than array, you get an error
Which means it's usually safer to rely on assigning the return value to a variable first and then doing your validation there for safety... Unless you are sure the function will always return an array with that key and you know you won't need to reuse the array.
In PHP 5.4 and above:
print_r($test->results()['second']);
In older versions which you shouldn't be running as they are out of security maintenance:
$results = $test->results();
print_r($results['second']);
Edit: The first example originally said 5.6 and above but array dereferencing was introduced in 5.4! For the avoidance of doubt, 5.6 is the lowest php version within security maintenance.

With php finding out if variable is this exact class instance

I have run into trouble figuring out how to compare two variables which might contain the exact same class instance.
An abstract class (part of which is shown below) has a method fetch_mother() designed to identify the object that should contain it and return that or simply return itself because it is at the bottom of the stack. In theory, that stack should be no more than 5 deep.
Most instances represent things like categories.
With the get get_full_path() method:
Expected output was: [siteurl] /system/drafts/example-one/also-dev-notes/
Actual output was: [siteurl] /drafts/drafts/[snip]/drafts/drafts/example-one/also-dev-notes/
Which means that the sanity check kicks in and breaks a loop. It also means that I have not correctly tested for the returned object being the same as $this.
How can I confirm is $var===$this?
Code where the problem takes place:
<?php
namespace modules\content\classes;
use modules\core\interfaces as i;
use modules\core\classes as c;
abstract class content_object extends c\module_lib {
// vars
// ...
protected $mother;
protected $map
// ... code ...
public function get_object_map(){
return $this->map;
}
/**
* Get the stream holding this item
* #return \modules\content\classes\error|\modules\content\classes\content_object
*/
public function &fetch_mother(){
if(isset($this->mother) && is_object($this->mother)){
return $this->mother;
}
$mother = $this->module()->find_object_stream($this);
if(!($mother instanceof \modules\core\error) && is_object($mother) && $mother != $this){
$this->mother = $mother;
return $mother;
}else{
// I am my own mother ? \\
return $this;
}
}
protected function fetch_full_path_from_mother($path='',$sanity=10){
$map = $this->get_object_map();
$mother = $this->fetch_mother();
$path = $map . '/' . $path;
if($this==$mother || !is_object($mother) || $sanity<1){
return $path;
}
$sanity--;
return $mother->fetch_full_path_from_mother($path,$sanity);
}
public function get_full_path(){
$home = $this->get_core()->factory()->get_config('home');
return $home . $this->fetch_full_path_from_mother();
}
}
The answer here is non-obvious.
<?php
$foo = $this;
if($foo==$this){
echo 'It is';
}else{
echo 'It is not';
}
The output of the above would be It is.
That's because if the two objects are the same instance then the == comparison would be enough to determine this.
Likewise (as per the comments) spl_object_hash($mother)==spl_object_hash($this) is also true only if it is the same object. However, if another object with the same properties were created the above would be false because they are separate objects.
This question and answers deals with that exact same topic: spl_object_hash matches, objects not identical
The assumption in my question (which I did not see at first) is that the lookup function is acting as a factory and caching objects. The differential conclusion must be that a copy or second instance is being returned.
Thus, the problem must be with the fetch_mother() method.
(Further investigation did indeed show that this was the problem.)
Solutions include checking for matching properties (which in this case works as there are several unique fields pulled from the database) or comparing print_r output.
if(print_r($mother,true)==print_r($this,true)){
// code
}
That particular solution is ugly, inelegant and not very reliable.
A better solution would be to implement an object cache higher up the stack. (Which is what I will be proposing).
TL;DR: Objects with identical properties are still not the same.

Ordering Doctrine Collection based on associated Entity when it is not possible to use the #orderBy annotation

I would like to understand the best way to order a Doctrine Collection based on associated Entity. In this case, it is not possible to use the #orderBy annotation.
I have found 5 solutions on the Internet.
1) Adding a method to the AbstractEntity (according to Ian Belter https://stackoverflow.com/a/22183527/1148260)
/**
* This method will change the order of elements within a Collection based on the given method.
* It preserves array keys to avoid any direct access issues but will order the elements
* within the array so that iteration will be done in the requested order.
*
* #param string $property
* #param array $calledMethods
*
* #return $this
* #throws \InvalidArgumentException
*/
public function orderCollection($property, $calledMethods = array())
{
/** #var Collection $collection */
$collection = $this->$property;
// If we have a PersistentCollection, make sure it is initialized, then unwrap it so we
// can edit the underlying ArrayCollection without firing the changed method on the
// PersistentCollection. We're only going in and changing the order of the underlying ArrayCollection.
if ($collection instanceOf PersistentCollection) {
/** #var PersistentCollection $collection */
if (false === $collection->isInitialized()) {
$collection->initialize();
}
$collection = $collection->unwrap();
}
if (!$collection instanceOf ArrayCollection) {
throw new InvalidArgumentException('First argument of orderCollection must reference a PersistentCollection|ArrayCollection within $this.');
}
$uaSortFunction = function($first, $second) use ($calledMethods) {
// Loop through $calledMethods until we find a orderable difference
foreach ($calledMethods as $callMethod => $order) {
// If no order was set, swap k => v values and set ASC as default.
if (false == in_array($order, array('ASC', 'DESC')) ) {
$callMethod = $order;
$order = 'ASC';
}
if (true == is_string($first->$callMethod())) {
// String Compare
$result = strcasecmp($first->$callMethod(), $second->$callMethod());
} else {
// Numeric Compare
$difference = ($first->$callMethod() - $second->$callMethod());
// This will convert non-zero $results to 1 or -1 or zero values to 0
// i.e. -22/22 = -1; 0.4/0.4 = 1;
$result = (0 != $difference) ? $difference / abs($difference): 0;
}
// 'Reverse' result if DESC given
if ('DESC' == $order) {
$result *= -1;
}
// If we have a result, return it, else continue looping
if (0 !== (int) $result) {
return (int) $result;
}
}
// No result, return 0
return 0;
};
// Get the values for the ArrayCollection and sort it using the function
$values = $collection->getValues();
uasort($values, $uaSortFunction);
// Clear the current collection values and reintroduce in new order.
$collection->clear();
foreach ($values as $key => $item) {
$collection->set($key, $item);
}
return $this;
}
2) Creating a Twig extension, if you need the sorting just in a template (according to Kris https://stackoverflow.com/a/12505347/1148260)
use Doctrine\Common\Collections\Collection;
public function sort(Collection $objects, $name, $property = null)
{
$values = $objects->getValues();
usort($values, function ($a, $b) use ($name, $property) {
$name = 'get' . $name;
if ($property) {
$property = 'get' . $property;
return strcasecmp($a->$name()->$property(), $b->$name()->$property());
} else {
return strcasecmp($a->$name(), $b->$name());
}
});
return $values;
}
3) Transforming the collection into an array and then sorting it (according to Benjamin Eberlei https://groups.google.com/d/msg/doctrine-user/zCKG98dPiDY/oOSZBMabebwJ)
public function getSortedByFoo()
{
$arr = $this->arrayCollection->toArray();
usort($arr, function($a, $b) {
if ($a->getFoo() > $b->getFoo()) {
return -1;
}
//...
});
return $arr;
}
4) Using ArrayIterator to sort the collection (according to nifr https://stackoverflow.com/a/16707694/1148260)
$iterator = $collection->getIterator();
$iterator->uasort(function ($a, $b) {
return ($a->getPropery() < $b->getProperty()) ? -1 : 1;
});
$collection = new ArrayCollection(iterator_to_array($iterator));
5) Creating a service to gather the ordered collection and then replace the unordered one (I have not an example but I think it is pretty clear). I think this is the ugliest solution.
Which is the best solution according to you experience? Do you have other suggestions to order a collection in a more effective/elegant way?
Thank you very much.
Premise
You proposed 5 valid/decent solutions, but I think that all could be reduced down to two cases, with some minor variants.
We know that sorting is always O(NlogN), so all solution have theoretically the same performance. But since this is Doctrine, the number of SQL queries and the Hydration methods (i.e. converting data from array to object instance) are the bottlenecks.
So you need to choose the "best method", depending on when you need the entities to be loaded and what you'll do with them.
These are my "best solutions", and in a general case I prefer my solution A)
A) DQL in a loader/repository service
Similar to
None of your case (somehow with 5, see the final notes note). Alberto Fernández pointed you in the right direction in a comment.
Best when
DQL is (potentially) the fastest method, since delegate sorting to DBMS which is highly optimized for this. DQL also gives total controls on which entities to fetch in a single query and the hydrations mode.
Drawbacks
It is not possible (AFAIK) to modify query generated by Doctrine Proxy classes by configuration, so your application need to use a Repository and call the proper method every time you load your entities (or override the default one).
Example
class MainEntityRepository extends EntityRepository
{
public function findSorted(array $conditions)
{
$qb = $this->createQueryBuilder('e')
->innerJoin('e.association', 'a')
->orderBy('a.value')
;
// if you always/frequently read 'a' entities uncomment this to load EAGER-ly
// $qb->select('e', 'a');
// If you just need data for display (e.g. in Twig only)
// return $qb->getQuery()->getResult(Query::HYDRATE_ARRAY);
return $qb->getQuery()->getResult();
}
}
B) Eager loading, and sorting in PHP
Similar to case
Case 2), 3) and 4) are just the same thing done in different place. My version is a general case which apply whenever the entities are fetched. If you have to choose one of these, then I think that solution 3) is the most convenient, since don't mess with the entity and is always available, but use EAGER loading (read on).
Best when
If the the associated entities are always read, but it is not possible (or convenient) to add a service, then all entities should loaded EAGER-ly. Sorting then can be done by PHP, whenever it makes sense for the application: in an event listener, in a controller, in a twig template... If the entities should be always loaded, then an event listener is the best option.
Drawbacks
Less flexible than DQL, and sorting in PHP may be a slow operation when the collection is big. Also, the entities need to be hydrated as Object which is slow, and is overkill if the collection is not used for other purpose. Beware of lazy-loading, since this will trigger one query for every entity.
Example
MainEntity.orm.xml:
<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping>
<entity name="MainEntity">
<id name="id" type="integer" />
<one-to-many field="collection" target-entity="LinkedEntity" fetch="EAGER" />
<entity-listeners>
<entity-listener class="MainEntityListener"/>
</entity-listeners>
</entity>
</doctrine-mapping>
MainEntity.php:
class MainEntityListener
{
private $id;
private $collection;
public function __construct()
{
$this->collection = new ArrayCollection();
}
// this works only with Doctrine 2.5+, in previous version association where not loaded on event
public function postLoad(array $conditions)
{
/*
* From your example 1)
* Remember that $this->collection is an ArryCollection when constructor is called,
* but a PersistentCollection when are loaded from DB. Don't recreate the instance!
*/
// Get the values for the ArrayCollection and sort it using the function
$values = $this->collection->getValues();
// sort as you like
asort($values);
// Clear the current collection values and reintroduce in new order.
$collection->clear();
foreach ($values as $key => $item) {
$collection->set($key, $item);
}
}
}
Final Notes
I won't use case 1) as is, since is very complicated and introduce inheritance which reduce encapsulation. Also, I think that it has the same complexity and performance of my example.
Case 5) is not necessarily bad. If "the service" is the application repository, and it use DQL to sort, then is my first best case. If is a custom service only to sort a collection, then I think is definitely not a good solution.
All the codes I wrote here is not ready for "copy-paste", since my objective was to show my point of view. Hope it would be a good starting point.
Disclaimer
These are "my" best solutions, as I do it in my works. Hope will help you and others.

Doctrine2: Syncing Collections / Adding multiple Elements of which some might already exist

So i have a string, representing several objects (tags in this case)
i.e.: "php,mysql,doctrine2"
Let's say my database already has "php" and "doctrine2".
Now i want the best way to add the missing elemets (in this case mysql).
Should i create an object for every element and just use persist/sync or something, or is there a better way?
I need all the objects at the end anyway to add them to a new object (with a simple many-to-many relation) anyway.
I'd be happy about any suggestions.
1) Pull out all your tag names with a single query into an array
2) Use array_filter along with a closure to detect tags not present in the dataset
3) Create an insert for the new tags
$currentTags = getCurrentTagsArray();
$newTags = explode(',', 'php,mysql,doctrine2');
$newTagsToSave = array_filter($currentTags, function($item) use ($newTags){
if (in_array($item, $newTags))
{
return false;
}
return true;
});
Or...
You can use Doctrine 2's ArrayCollection wrapper (\Doctrine\Common\Collections\ArrayCollection()) it has pretty much the same implementation above as a filter method (you still need to pass the closure).
$myCollection->filter($closure);
I had a similar problem where I had to synchronize an entity collection with an external source. However, my problem required not only additions, but also updates and deletes. I used code to diff the ArrayCollection with another array, and call CRUD methods add based on the differences. As far as I can tell from the docs, doctrine doesn't natively handle this. Average performance should be O(n) but takes some memory.
/**
* #param array $source - the array we are starting with
* #param array $new - the array we want to end with
* #param $fnHash - function used to determine object equality, not based on object id
* #param $fnUpdate - function to perform update of existing object, takes current object and new object as params
* #param $fnAdd - function to perform insert
* #param $fnDelete - function to perform delete
*/
public static function syncArrays(array $source, array $new,
$fnHash, $fnUpdate, $fnAdd, $fnDelete)
{
// make modifiable array copies mapped by hashes of the elements
$sourceKeys = array_map($fnHash, $source);
$hasKeys =count($sourceKeys) > 0;
$newarray = ($hasKeys) ? array_combine(array_map($fnHash, $new), $new) : $new;
if ($hasKeys) { // true => may have updates or deletes
$sourcearray = array_combine($sourceKeys, $source);
// updates
foreach ($sourceKeys as $hashkey) {
if (isset($sourcearray[$hashkey]) && isset($newarray[$hashkey])) {
$fnUpdate($sourcearray[$hashkey], $newarray[$hashkey]);
unset($sourcearray[$hashkey]);
unset($newarray[$hashkey]);
}
}
// deletes
foreach ($sourcearray as $entity) {
$fnDelete($entity);
}
}
//adds
foreach ($newarray as $entity) {
$fnAdd($entity);
}
}
The way I call it to update my doctrine association $parentEntity->getPayments() is:
ArrayHelper::syncArrays($parentEntity->getPayments()->toArray(), $newPayments,
function($entity) {return $a->getName();}, // hash function
function($current, $new) {
$current->setTotal($new->getTotal()); // update function
},
function($a) use ($parent, $manager) {
$parent->addVendorPaymentObject($a); // add function
$manager->persist($a);
},
function($a) use ($manager) { // delete function
$manager->remove($a);
}
);

Sorting an array of objects in PHP

I want to write a static method in a class to generically sort an array of objects.
I am thinking of somthing along the lines of:
class GenUtils {
const ASCENDING = 1;
const DESCENDING = 2;
protected static alphaSort($value1, $value2, $sort_type=self::DESCENDING){
$retval = strcasecmp($value1, $value2);
return ($sort_type == self::DESCENDING) ? $retval : (-1*$retval);
}
protected static numericSort($value1, $value2, $sort_type=self::DESCENDING){
return $value1 < $value2;
}
// Assumption: array is non-empty and homogeneous
public doSort(array& $object_array, $method_name, $sort_type=self::DESCENDING) {
if(!empty($object_array) && method_exists($object_array[0],$method_name)) {
$element = $object_array[0];
$value = $element->$method_name();
if(is_string($value)){
//do string sort (possibly using usort)
}
elseif(is_number($value)){
//do numeric sort (possibly using usort)
}
}
}
}
This is just a quick brain dump -perharps someone can fill in the missing pieces, or suggest a better way of doing this?
[Edit]
Just to clarify, the objects to be sorted (in the array), have methods which return either a string (e.g. getName()) or a numeric value (e.g. getId())
A typical usecase code snippet would therefore be somethimng like this:
GenUtils::doSort($objects,'getName'); // This will do an alphabetic DESC sort using the getName() method
GenUtils::doSort($objects, 'getId', GenUtils::ASCENDING); // This will do a numeric ASC sort using the getId() method
The use cases (numeric and string) in your example are already built in to PHP - Check out the sort function. I would use the built-in function unless I had more specific needs.
Use usort and define your own comparison function to work with your objects.

Categories