What is an elegant way to remove an object from an array of objects in PHP?
class Data{
private $arrObservers;
public add(Observer $o) {
array_push($this->arrObservers, $o);
}
public remove(Observer $o) {
// I NEED THIS CODE to remove $o from $this->arrObservers
}
}
You can do
function unsetValue(array $array, $value, $strict = TRUE)
{
if(($key = array_search($value, $array, $strict)) !== FALSE) {
unset($array[$key]);
}
return $array;
}
You can also use spl_object_hash to create a hash for the objects and use that as array key.
However, PHP also has a native Data Structure for Object collections with SplObjectStorage:
$a = new StdClass; $a->id = 1;
$b = new StdClass; $b->id = 2;
$c = new StdClass; $c->id = 3;
$storage = new SplObjectStorage;
$storage->attach($a);
$storage->attach($b);
$storage->attach($c);
echo $storage->count(); // 3
// trying to attach same object again
$storage->attach($c);
echo $storage->count(); // still 3
var_dump( $storage->contains($b) ); // TRUE
$storage->detach($b);
var_dump( $storage->contains($b) ); // FALSE
SplObjectStorage is Traversable, so you can foreach over it as well.
On a sidenote, PHP also has native interfaces for Subject and Observer.
I agree with the answers above, but for the sake of completeness (where you may not have unique IDs to use as a key) my preferred methods of removing values from an array are as follows:
/**
* Remove each instance of a value within an array
* #param array $array
* #param mixed $value
* #return array
*/
function array_remove(&$array, $value)
{
return array_filter($array, function($a) use($value) {
return $a !== $value;
});
}
/**
* Remove each instance of an object within an array (matched on a given property, $prop)
* #param array $array
* #param mixed $value
* #param string $prop
* #return array
*/
function array_remove_object(&$array, $value, $prop)
{
return array_filter($array, function($a) use($value, $prop) {
return $a->$prop !== $value;
});
}
Which are used in the following way:
$values = array(
1, 2, 5, 3, 5, 6, 7, 1, 2, 4, 5, 6, 6, 8, 8,
);
print_r(array_remove($values, 6));
class Obj {
public $id;
public function __construct($id) {
$this->id = $id;
}
}
$objects = array(
new Obj(1), new Obj(2), new Obj(4), new Obj(3), new Obj(6), new Obj(4), new Obj(3), new Obj(1), new Obj(5),
);
print_r(array_remove_object($objects, 1, 'id'));
I recommend using the ID (if you have one, anything that will be unique to that object should work within reason) of the object as the array key. This way you can address the object within the array without having to run through a loop or store the ID in another location. The code would look something like this:
$obj_array[$obj1->getId()] = $obj1;
$obj_array[$obj2->getId()] = $obj2;
$obj_array[$obj3->getId()] = $obj3;
unset($obj_array[$object_id]);
UPDATE:
class Data{
private $arrObservers;
public add(Observer $o) {
$this->arrObservers[$o->getId()] = $o;
}
public remove(Observer $o) {
unset($this->arrObservers[$o->getId()]);
}
}
unset($myArray[$index]); where $index is the index of the element you want to remove. If you wan't a more specific answer, show some code or describe what you're trying to do.
$obj_array['obj1'] = $obj1;
$obj_array['obj2'] = $obj2;
$obj_array['obj3'] = $obj3;
unset($obj_array['obj3']);
For remove an object from a multi dimensional array you can use this:
$exampleArray= [
[
"myKey"=>"This is my key",
"myValue"=>"10"
],
[
"myKey"=>"Oh!",
"myValue"=>"11"
]
];
With array_column you can specify your key column name:
if(($key = array_search("Oh!", array_column($exampleArray, 'myKey'))) !== false) {
unset($exampleArray[$key]);
}
And this will remove the indicated object.
Use this for your internal object storage instead: http://us2.php.net/manual/en/class.splobjectstorage.php
function obj_array_clean ($array, $objId)
{
$new = array() ;
foreach($array as $value)
{
$new[$value->{$objId}] = $value;
}
$array = array_values($new);
return $array;
}
$ext2 = obj_array_clean($ext, 'OnjId');
It will remove the duplicate object "OnjId" from array objects $array.
If you want to remove one or more objects from array of objects (using spl_object_hash to determine if objects are the same) you can use this method:
$this->arrObservers = Arr::diffObjects($this->arrObservers, [$o]);
from this library.
Reading the Observer pattern part of the GoF book? Here's a solution that will eliminate the need to do expensive searching to find the index of the object that you want to remove.
public function addObserver(string $aspect, string $viewIndex, Observer $view)
{
$this->observers[$aspect][$viewIndex] = $view;
}
public function removeObserver(string $aspect, string $viewIndex)
{
if (!isset($this->observers[$aspect])) {
throw new OutOfBoundsException("No such aspect ({$aspect}) of this Model exists: " . __CLASS__);
}
if (!isset($this->observers[$aspect][$viewIndex])) {
throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to the aspect ({$aspect}) of this Model:" . __CLASS__);
}
unset($this->observers[$aspect][$viewIndex]);
}
You can loose the "aspect" dimension if you are not using that way of keeping track of which Views are updated by specific Models.
public function addObserver(string $viewIndex, Observer $view)
{
$this->observers[$viewIndex] = $view;
}
public function removeObserver(string $viewIndex)
{
if (!isset($this->observers[$viewIndex])) {
throw new OutOfBoundsException("No such View for ({$viewIndex}) was added to this Model:" . __CLASS__);
}
unset($this->observers[$viewIndex]);
}
Summary
Build in a way to find the element before assigning the object to the array. Otherwise, you will have to discover the index of the object element first.
If you have a large number of object elements (or, even more than a handful), then you may need to resort to finding the index of the object first. The PHP function array_search() is one way to start with a value, and get the index/key in return.
https://www.php.net/manual/en/function.array-search.php
Do be sure to use the strict argument when you call the function.
If the third parameter strict is set to true then the array_search()
function will search for identical elements in the haystack. This
means it will also perform a strict type comparison of the needle in
the haystack, and objects must be the same instance.
Try this, will solve your problem.
class Data{
private $arrObservers;
public add(Observer $o) {
array_push($this->arrObservers,$o);
}
public remove($Observer $o) {
unset($this->arrObservers[$o]);
}
}
I believe this is the best way
$index = array_search($o, $this->arrObservers, true);
unset($this->arrObservers[$index]);
Related
I'm trying to iterate an array with next() current() and rewind():
is there a simple way to know when current pointer is arrived at the end ?
I just seen that when i'm at the end next() and current() returns both FALSE, but this also append when the array cells contain the FALSE boolean value.
edit:
of course sorry to not have specified that it's a numerical array.
I use it to and i'm trying to do a wordpress like
while ( have_post() )
do_post()
but the origin can be both an array or a PDOStatement, like the follow:
class Document implements ContentGetter{
private $array_or_rs;
private $currentval;
public function have_content() { //part of interface
if( is_a($this->$array_or_rs, 'PDOStatement') ) {
$result = PDOStatement-fetch();
_do_things_
} else {
$result = next($this->$array_or_rs);
}
$this->$currentval = $result;
return $result === FALSE;
}
public function get_content() { //part of interface
return $this->$currentval;
}
}
Can I iterate over an array without using a foreach loop in PHP?
Here you go:
$array = array(NULL, FALSE, 0);
while(list($key, $value) = each($array)) {
var_dump($value);
}
Output:
NULL
bool(false)
int(0)
next() and current() can't be used because there is no way to determine if a FALSE return value means a FALSE element or the end of the array. (As you've observed). However, you can use the function each(), as it will return an array containing the current key and value or FALSE at the end of the array. After it has been executed, the current-pointer will be set to the next element.
I must admit that I've not used that since > 10 years. :) However it is a basic PHP function and it still works.
How to implement the WordPres-like ContentGetter interface?
I would make usage of PHP's Iterator concept. You can wrap either the array in to an ArrayIterator or the PDOStatement into a PDOStatementIterator. While this first is a built-in class of PHP, the latter has to be written. Alternatively you can use this one (looks good, but contains more functionality than required for this task)
Based on that, the Document class should look like this:
class Document implements ContentGetter {
/**
* #var Iterator
*/
protected $iterator;
/**
* #param array|PDOStatement $stmtOrArray
*/
public function __construct($stmtOrArray) {
if(is_a($stmtOrArray, 'PDOStatement')) {
$this->iterator = new PDOStatementIterator($stmtOrArray);
} else if(is_array($stmtOrArray)) {
$this->iterator = new ArrayIterator($stmtOrArray);
} else {
throw new Exception('Expected array or PDOStatement');
}
}
/**
* Wrapper for Iterator::valid()
*/
public function have_content() {
return $this->iterator->valid();
}
/**
* Wrapper for Iterator::current() + Iterator::next()
*/
public function get_content() {
$item = $this->iterator->current();
$this->iterator->next();
return $item;
}
}
Tests:
// Feed Document with a PDOStatement
$pdo = new PDO('mysql:host=localhost', 'user', 'password');
$result = $pdo->query('SELECT 1 UNION SELECT 2'); // stupid query ...
$doc = new Document($result);
while($doc->have_content()) {
var_dump($doc->get_content());
}
.
// Feed Document with an array
$doc = new Document(array(1, 2, 3));
while($doc->have_content()) {
var_dump($doc->get_content());
}
you could compare key() to sizeof($my_array)
i use
$myblogrepo = $this->_doctrine->getRepository('Entities\Blog')->findBy(array('id' => 12);
i access via
foreach($myblogrepo as $key =>$value){
echo $key . $value;
}
how can i get the field names? i thought the key => would work but it print s the key as 0
so i thought this would work:
foreach($myblogrepo[0] as $key =>$value){
echo $key . $value;
}
but still nothing..
}
In all likelihood, your Blog entity's properties are declared as protected. This is why you can't iterate over them from outside the the Entity itself.
If you're using your Blog entities in a read-only fashion, and only need access to the properties marked as #Columns (read: you don't need to call any methods on your entity), you might consider using array-hydration. That way you'll be dealing with simple arrays, and $k=>$v type iteration will work fine.
Otherwise, you'll need to create some kind of getValues() method on your entity class. This could be a simple implementation that just builds and array and returns it.
Finally, you could create a general-purpose getValues() as a utility function that uses doctrine's class metadata to figure out what columns and entity has, and operate on those data. A simple implementation like this:
function getEntityColumnValues($entity,$em){
$cols = $em->getClassMetadata(get_class($entity))->getColumnNames();
$values = array();
foreach($cols as $col){
$getter = 'get'.ucfirst($col);
$values[$col] = $entity->$getter();
}
return $values;
}
EDIT - A more mature version of the above method seems to be available here - I've not played with it yet, but it looks promising.
If you just need to get the properties of the entity in a fast and easy way, this is what I do in my projects:
All my entities inherit from an EntityBase class, which has the following method:
public function toValueObject()
{
$result = new \stdClass();
foreach ($this as $property => $value) {
$getter = 'get' . ucfirst($property);
if (method_exists($this, $getter)) {
$result->$property = $this->$getter();
}
}
return $result;
}
So all I have to do is call $entity->toValueObject() and I obtain a standard object with all of the entity's properties as public properties.
Use findOneBy instead of findBy to select a single row.
$myblogrepo = $this->_doctrine->getRepository('Entities\Blog')->findOneBy(array('id' => 12);
Your key was 0 because it was the first row in a possible multi-row result.
This is a my implementation of a serializer class that also check if it is a doctrine entity:
/**
* JsonApiSerializer constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
/**
* #param $payLoad
* #return string
*/
public function serialize($payLoad, $type)
{
$serializedPayload = new \stdClass();
$serializedPayload->data = new \stdClass();
$serializedPayload->data->type = $type;
if ($this->isDoctrineEntity($payLoad)) {
$this->addEntityColumnValues($serializedPayload, $payLoad);
}
return json_encode($serializedPayload);
}
private function isDoctrineEntity($payLoad)
{
if (is_object($payLoad)) {
$payLoad = ($payLoad instanceof Proxy)
? get_parent_class($payLoad)
: get_class($payLoad);
}
return !$this->em->getMetadataFactory()->isTransient($payLoad);
}
private function addEntityColumnValues(&$serializedPayload, $entity){
$serializedPayload->data->attributes = new \stdClass();
$classMetaData = $this->em->getClassMetadata(get_class($entity));
$columnNames = $classMetaData->getColumnNames();
foreach($columnNames as $columnName){
$fieldName = $classMetaData->getFieldForColumn($columnName);
$getter = 'get'.ucfirst($fieldName);
$serializedPayload->data->attributes->$columnName = $entity->$getter();
}
}
Is there any method like the array_unique for objects? I have a bunch of arrays with 'Role' objects that I merge, and then I want to take out the duplicates :)
array_unique works with an array of objects using SORT_REGULAR:
class MyClass {
public $prop;
}
$foo = new MyClass();
$foo->prop = 'test1';
$bar = $foo;
$bam = new MyClass();
$bam->prop = 'test2';
$test = array($foo, $bar, $bam);
print_r(array_unique($test, SORT_REGULAR));
Will print:
Array (
[0] => MyClass Object
(
[prop] => test1
)
[2] => MyClass Object
(
[prop] => test2
)
)
See it in action here: http://3v4l.org/VvonH#v529
Warning: it will use the "==" comparison, not the strict comparison ("===").
So if you want to remove duplicates inside an array of objects, beware that it will compare each object properties, not compare object identity (instance).
Well, array_unique() compares the string value of the elements:
Note: Two elements are considered equal if and only if (string) $elem1 === (string) $elem2 i.e. when the string representation is the same, the first element will be used.
So make sure to implement the __toString() method in your class and that it outputs the same value for equal roles, e.g.
class Role {
private $name;
//.....
public function __toString() {
return $this->name;
}
}
This would consider two roles as equal if they have the same name.
This answer uses in_array() since the nature of comparing objects in PHP 5 allows us to do so. Making use of this object comparison behaviour requires that the array only contain objects, but that appears to be the case here.
$merged = array_merge($arr, $arr2);
$final = array();
foreach ($merged as $current) {
if ( ! in_array($current, $final)) {
$final[] = $current;
}
}
var_dump($final);
Here is a way to remove duplicated objects in an array:
<?php
// Here is the array that you want to clean of duplicate elements.
$array = getLotsOfObjects();
// Create a temporary array that will not contain any duplicate elements
$new = array();
// Loop through all elements. serialize() is a string that will contain all properties
// of the object and thus two objects with the same contents will have the same
// serialized string. When a new element is added to the $new array that has the same
// serialized value as the current one, then the old value will be overridden.
foreach($array as $value) {
$new[serialize($value)] = $value;
}
// Now $array contains all objects just once with their serialized version as string.
// We don't care about the serialized version and just extract the values.
$array = array_values($new);
You can also serialize first:
$unique = array_map( 'unserialize', array_unique( array_map( 'serialize', $array ) ) );
As of PHP 5.2.9 you can just use optional sort_flag SORT_REGULAR:
$unique = array_unique( $array, SORT_REGULAR );
You can also use they array_filter function, if you want to filter objects based on a specific attribute:
//filter duplicate objects
$collection = array_filter($collection, function($obj)
{
static $idList = array();
if(in_array($obj->getId(),$idList)) {
return false;
}
$idList []= $obj->getId();
return true;
});
From here: http://php.net/manual/en/function.array-unique.php#75307
This one would work with objects and arrays also.
<?php
function my_array_unique($array, $keep_key_assoc = false)
{
$duplicate_keys = array();
$tmp = array();
foreach ($array as $key=>$val)
{
// convert objects to arrays, in_array() does not support objects
if (is_object($val))
$val = (array)$val;
if (!in_array($val, $tmp))
$tmp[] = $val;
else
$duplicate_keys[] = $key;
}
foreach ($duplicate_keys as $key)
unset($array[$key]);
return $keep_key_assoc ? $array : array_values($array);
}
?>
If you have an indexed array of objects, and you want to remove duplicates by comparing a specific property in each object, a function like the remove_duplicate_models() one below can be used.
class Car {
private $model;
public function __construct( $model ) {
$this->model = $model;
}
public function get_model() {
return $this->model;
}
}
$cars = [
new Car('Mustang'),
new Car('F-150'),
new Car('Mustang'),
new Car('Taurus'),
];
function remove_duplicate_models( $cars ) {
$models = array_map( function( $car ) {
return $car->get_model();
}, $cars );
$unique_models = array_unique( $models );
return array_values( array_intersect_key( $cars, $unique_models ) );
}
print_r( remove_duplicate_models( $cars ) );
The result is:
Array
(
[0] => Car Object
(
[model:Car:private] => Mustang
)
[1] => Car Object
(
[model:Car:private] => F-150
)
[2] => Car Object
(
[model:Car:private] => Taurus
)
)
sane and fast way if you need to filter duplicated instances (i.e. "===" comparison) out of array and:
you are sure what array holds only objects
you dont need keys preserved
is:
//sample data
$o1 = new stdClass;
$o2 = new stdClass;
$arr = [$o1,$o1,$o2];
//algorithm
$unique = [];
foreach($arr as $o){
$unique[spl_object_hash($o)]=$o;
}
$unique = array_values($unique);//optional - use if you want integer keys on output
This is very simple solution:
$ids = array();
foreach ($relate->posts as $key => $value) {
if (!empty($ids[$value->ID])) { unset($relate->posts[$key]); }
else{ $ids[$value->ID] = 1; }
}
You can also make the array unique using a callback function (e.g. if you want to compare a property of the object or whatever method).
This is the generic function I use for this purpose:
/**
* Remove duplicate elements from an array by comparison callback.
*
* #param array $array : An array to eliminate duplicates by callback
* #param callable $callback : Callback accepting an array element returning the value to compare.
* #param bool $preserveKeys : Add true if the keys should be perserved (note that if duplicates eliminated the first key is used).
* #return array: An array unique by the given callback
*/
function unique(array $array, callable $callback, bool $preserveKeys = false): array
{
$unique = array_intersect_key($array, array_unique(array_map($callback, $array)));
return ($preserveKeys) ? $unique : array_values($unique);
}
Sample usage:
$myUniqueArray = unique($arrayToFilter,
static function (ExamQuestion $examQuestion) {
return $examQuestion->getId();
}
);
array_unique version for strict (===) comparison, preserving keys:
function array_unique_strict(array $array): array {
$result = [];
foreach ($array as $key => $item) {
if (!in_array($item, $result, true)) {
$result[$key] = $item;
}
}
return $result;
}
Usage:
class Foo {}
$foo1 = new Foo();
$foo2 = new Foo();
array_unique_strict( ['a' => $foo1, 'b' => $foo1, 'c' => $foo2] ); // ['a' => $foo1, 'c' => $foo2]
array_unique works by casting the elements to a string and doing a comparison. Unless your objects uniquely cast to strings, then they won't work with array_unique.
Instead, implement a stateful comparison function for your objects and use array_filter to throw out things the function has already seen.
This is my way of comparing objects with simple properties, and at the same time receiving a unique collection:
class Role {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$roles = [
new Role('foo'),
new Role('bar'),
new Role('foo'),
new Role('bar'),
new Role('foo'),
new Role('bar'),
];
$roles = array_map(function (Role $role) {
return ['key' => $role->getName(), 'val' => $role];
}, $roles);
$roles = array_column($roles, 'val', 'key');
var_dump($roles);
Will output:
array (size=2)
'foo' =>
object(Role)[1165]
private 'name' => string 'foo' (length=3)
'bar' =>
object(Role)[1166]
private 'name' => string 'bar' (length=3)
If you have array of objects and you want to filter this collection to remove all duplicates you can use array_filter with anonymous function:
$myArrayOfObjects = $myCustomService->getArrayOfObjects();
// This is temporary array
$tmp = [];
$arrayWithoutDuplicates = array_filter($myArrayOfObjects, function ($object) use (&$tmp) {
if (!in_array($object->getUniqueValue(), $tmp)) {
$tmp[] = $object->getUniqueValue();
return true;
}
return false;
});
Important: Remember that you must pass $tmp array as reference to you filter callback function otherwise it will not work
I'm using a third party storage system that only returns me stdClass objects no matter what I feed in for some obscure reason. So I'm curious to know if there is a way to cast/convert an stdClass object into a full fledged object of a given type.
For instance something along the lines of:
//$stdClass is an stdClass instance
$converted = (BusinessClass) $stdClass;
I am just casting the stdClass into an array and feed it to the BusinessClass constructor, but maybe there is a way to restore the initial class that I am not aware of.
Note: I am not interested in 'Change your storage system' type of answers since it is not the point of interest. Please consider it more an academic question on the language capacities.
Cheers
See the manual on Type Juggling on possible casts.
The casts allowed are:
(int), (integer) - cast to integer
(bool), (boolean) - cast to boolean
(float), (double), (real) - cast to float
(string) - cast to string
(array) - cast to array
(object) - cast to object
(unset) - cast to NULL (PHP 5)
You would have to write a Mapper that does the casting from stdClass to another concrete class. Shouldn't be too hard to do.
Or, if you are in a hackish mood, you could adapt the following code:
function arrayToObject(array $array, $className) {
return unserialize(sprintf(
'O:%d:"%s"%s',
strlen($className),
$className,
strstr(serialize($array), ':')
));
}
which pseudocasts an array to an object of a certain class. This works by first serializing the array and then changing the serialized data so that it represents a certain class. The result is unserialized to an instance of this class then. But like I said, it's hackish, so expect side-effects.
For object to object, the code would be
function objectToObject($instance, $className) {
return unserialize(sprintf(
'O:%d:"%s"%s',
strlen($className),
$className,
strstr(strstr(serialize($instance), '"'), ':')
));
}
You can use above function for casting not similar class objects (PHP >= 5.3)
/**
* Class casting
*
* #param string|object $destination
* #param object $sourceObject
* #return object
*/
function cast($destination, $sourceObject)
{
if (is_string($destination)) {
$destination = new $destination();
}
$sourceReflection = new ReflectionObject($sourceObject);
$destinationReflection = new ReflectionObject($destination);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
if ($destinationReflection->hasProperty($name)) {
$propDest = $destinationReflection->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($destination,$value);
} else {
$destination->$name = $value;
}
}
return $destination;
}
EXAMPLE:
class A
{
private $_x;
}
class B
{
public $_x;
}
$a = new A();
$b = new B();
$x = cast('A',$b);
$x = cast('B',$a);
To move all existing properties of a stdClass to a new object of a specified class name:
/**
* recast stdClass object to an object with type
*
* #param string $className
* #param stdClass $object
* #throws InvalidArgumentException
* #return mixed new, typed object
*/
function recast($className, stdClass &$object)
{
if (!class_exists($className))
throw new InvalidArgumentException(sprintf('Inexistant class %s.', $className));
$new = new $className();
foreach($object as $property => &$value)
{
$new->$property = &$value;
unset($object->$property);
}
unset($value);
$object = (unset) $object;
return $new;
}
Usage:
$array = array('h','n');
$obj=new stdClass;
$obj->action='auth';
$obj->params= &$array;
$obj->authKey=md5('i');
class RestQuery{
public $action;
public $params=array();
public $authKey='';
}
$restQuery = recast('RestQuery', $obj);
var_dump($restQuery, $obj);
Output:
object(RestQuery)#2 (3) {
["action"]=>
string(4) "auth"
["params"]=>
&array(2) {
[0]=>
string(1) "h"
[1]=>
string(1) "n"
}
["authKey"]=>
string(32) "865c0c0b4ab0e063e5caa3387c1a8741"
}
NULL
This is limited because of the new operator as it is unknown which parameters it would need. For your case probably fitting.
I have a very similar problem. Simplified reflection solution worked just fine for me:
public static function cast($destination, \stdClass $source)
{
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
$destination->{$name} = $source->$name;
}
return $destination;
}
Hope that somebody find this useful
// new instance of stdClass Object
$item = (object) array(
'id' => 1,
'value' => 'test object',
);
// cast the stdClass Object to another type by passing
// the value through constructor
$casted = new ModelFoo($item);
// OR..
// cast the stdObject using the method
$casted = new ModelFoo;
$casted->cast($item);
class Castable
{
public function __construct($object = null)
{
$this->cast($object);
}
public function cast($object)
{
if (is_array($object) || is_object($object)) {
foreach ($object as $key => $value) {
$this->$key = $value;
}
}
}
}
class ModelFoo extends Castable
{
public $id;
public $value;
}
Changed function for deep casting (using recursion)
/**
* Translates type
* #param $destination Object destination
* #param stdClass $source Source
*/
private static function Cast(&$destination, stdClass $source)
{
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
if (gettype($destination->{$name}) == "object") {
self::Cast($destination->{$name}, $source->$name);
} else {
$destination->{$name} = $source->$name;
}
}
}
consider adding a new method to BusinessClass:
public static function fromStdClass(\stdClass $in): BusinessClass
{
$out = new self();
$reflection_object = new \ReflectionObject($in);
$reflection_properties = $reflection_object->getProperties();
foreach ($reflection_properties as $reflection_property)
{
$name = $reflection_property->getName();
if (property_exists('BusinessClass', $name))
{
$out->{$name} = $in->$name;
}
}
return $out;
}
then you can make a new BusinessClass from $stdClass:
$converted = BusinessClass::fromStdClass($stdClass);
And yet another approach using the decorator pattern and PHPs magic getter & setters:
// A simple StdClass object
$stdclass = new StdClass();
$stdclass->foo = 'bar';
// Decorator base class to inherit from
class Decorator {
protected $object = NULL;
public function __construct($object)
{
$this->object = $object;
}
public function __get($property_name)
{
return $this->object->$property_name;
}
public function __set($property_name, $value)
{
$this->object->$property_name = $value;
}
}
class MyClass extends Decorator {}
$myclass = new MyClass($stdclass)
// Use the decorated object in any type-hinted function/method
function test(MyClass $object) {
echo $object->foo . '<br>';
$object->foo = 'baz';
echo $object->foo;
}
test($myclass);
Yet another approach.
The following is now possible thanks to the recent PHP 7 version.
$theStdClass = (object) [
'a' => 'Alpha',
'b' => 'Bravo',
'c' => 'Charlie',
'd' => 'Delta',
];
$foo = new class($theStdClass) {
public function __construct($data) {
if (!is_array($data)) {
$data = (array) $data;
}
foreach ($data as $prop => $value) {
$this->{$prop} = $value;
}
}
public function word4Letter($letter) {
return $this->{$letter};
}
};
print $foo->word4Letter('a') . PHP_EOL; // Alpha
print $foo->word4Letter('b') . PHP_EOL; // Bravo
print $foo->word4Letter('c') . PHP_EOL; // Charlie
print $foo->word4Letter('d') . PHP_EOL; // Delta
print $foo->word4Letter('e') . PHP_EOL; // PHP Notice: Undefined property
In this example, $foo is being initialized as an anonymous class that takes one array or stdClass as only parameter for the constructor.
Eventually, we loop through the each items contained in the passed object and dynamically assign then to an object's property.
To make this approch event more generic, you can write an interface or a Trait that you will implement in any class where you want to be able to cast an stdClass.
BTW: Converting is highly important if you are serialized, mainly because the de-serialization breaks the type of objects and turns into stdclass, including DateTime objects.
I updated the example of #Jadrovski, now it allows objects and arrays.
example
$stdobj=new StdClass();
$stdobj->field=20;
$obj=new SomeClass();
fixCast($obj,$stdobj);
example array
$stdobjArr=array(new StdClass(),new StdClass());
$obj=array();
$obj[0]=new SomeClass(); // at least the first object should indicates the right class.
fixCast($obj,$stdobj);
code: (its recursive). However, i don't know if its recursive with arrays. May be its missing an extra is_array
public static function fixCast(&$destination,$source)
{
if (is_array($source)) {
$getClass=get_class($destination[0]);
$array=array();
foreach($source as $sourceItem) {
$obj = new $getClass();
fixCast($obj,$sourceItem);
$array[]=$obj;
}
$destination=$array;
} else {
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
if (is_object(#$destination->{$name})) {
fixCast($destination->{$name}, $source->$name);
} else {
$destination->{$name} = $source->$name;
}
}
}
}
Convert it to an array, return the first element of that array, and set the return param to that class. Now you should get the autocomplete for that class as it will regconize it as that class instead of stdclass.
/**
* #return Order
*/
public function test(){
$db = new Database();
$order = array();
$result = $db->getConnection()->query("select * from `order` where productId in (select id from product where name = 'RTX 2070')");
$data = $result->fetch_object("Order"); //returns stdClass
array_push($order, $data);
$db->close();
return $order[0];
}
Is there a version of the PHP array class where all the elements must be distinct like, for instance, sets in Python?
Nope. You could fake it by using an associative array where the keys are the elements in the "set" and the values are ignored.
Here's a first-draft of an idea that could eventually work for what you want.
<?php
class DistinctArray implements IteratorAggregate, Countable, ArrayAccess
{
protected $store = array();
public function __construct(array $initialValues)
{
foreach ($initialValues as $key => $value) {
$this[$key] = $value;
}
}
final public function offsetSet( $offset, $value )
{
if (in_array($value, $this->store, true)) {
throw new DomainException('Values must be unique!');
}
if (null === $offset) {
array_push($this->store, $value);
} else {
$this->store[$offset] = $value;
}
}
final public function offsetGet($offset)
{
return $this->store[$offset];
}
final public function offsetExists($offset)
{
return array_key_exists($offset, $this->store);
}
final public function offsetUnset($offset)
{
unset( $this->store[$offset] );
}
final public function count()
{
return count($this->store);
}
final public function getIterator()
{
return new ArrayIterator($this->store);
}
}
$test = new DistinctArray(array(
'test' => 1,
'foo' => 2,
'bar' => 3,
'baz' => '1',
8 => 4,
));
try {
$test[] = 5;
$test[] = 6;
$test['dupe'] = 1;
}
catch (DomainException $e) {
echo "Oops! ", $e->getMessage(), "<hr>";
}
foreach ($test as $value) {
echo $value, '<br>';
}
You can use a special class, or array_unique to filter the duplicates.
An array is an array, and for the most part yo can put anything into it. All keys must be unique. If you want to go and add a function that strips out duplicate values, that is very possible by simply doing a array_unique statement
For objects that are not integers and strings: SplObjectStorage
The SplObjectStorage class provides a map from objects to data or, by ignoring data, an object set.
You can use this Set class. You can install with pecl
sudo pecl install ds
there is also a polyfill version available if u have no root access
composer require php-ds/php-ds
In a basic way you could try array_unique(), maybe this could help to avoid duplicates in your array.
You cannot use array_unique.
If you use int and string values, array_unique uses a string-representation for comparison, so array [1,'1','2'] will result in [1,'2'].
You can trick
Array Key index are unique.
So you can store unique val as string keys, like a python set.
$liste , is just a bad exemple for simulate datas.
$liste = ['1','1','2','3','4'];
$uniq = [];
for ($liste as elem) {
$uniq[$elem] = 1;
}
$uniq = array_keys($uniq);
// Or use directly
for ($uniq as $uniqVal => $null) {
echo $uniqVal;
}