Merging 2 custom objects - php

I need to merge 2 custom objects of the same type. Here's what the class looks like:
<?php
class a{
public $one;
public $two;
public function doSomething(){
//do stuff
}
}
I have got 2 instance of a that I need to merge together. I understand that I can use array_merge like this:
$result = (object)array_merge((array)$a1, (array)$a2);
But the problem is that I need the result to be of the a class and not stdObj.
If I do:
$result->doSomething()
an error results: PHP Fatal error: Call to undefined method stdClass::doSomething()
Since we cannot type cast to non-primitives, one cannot do: $result = (a)array_merge((array)$a1, (array)$a2);
Besides using a loop to iterate through one object and get and set values, are there more performant or neater ways to do this?

array_merge() is not what you want. As you rightly point out, they are not arrays
It sounds to me like you need what C++ calls a copy constructor.
Bascially, you are going to have to code it yourself, so add a function called merge() to your class which takes a single paramter which is an other obect of that class.
To code it you just need to assign each individual field of the parameter's data to your object's data.
read the PHP manual on object cloning for more info.

Related

PHP callback: Is there an equivalent for ::class for a method of a class?

In PHP it is possible to get a full class name via class name resolution like this:
Example:
namespace Name\Space;
class ClassName {}
echo ClassName::class;
Output: Name\Space\ClassName
This is better than using the string Name\Space\ClassName directly in the code because code introspection especially in IDEs can find an error directly.
I wonder if there is something similar for methods of a class - this would be specifically useful for callback functions.
This is how you can basically can pass a callback:
$a = function($callback,$arg) { return $callback($arg); }
$a('getInfo',5);
Instead of passing a string here (which might change), I would prefer to do something like this:
$a(MyClass::class::getInfo,5);
With I "go to declaration" click in the IDE I could go directly to getInfo plus I see errors in case with method does not exist anymore. Is there a way to achieve what I want to do here?
In fact, you work with callable type. And PHP allows setting method/function name only as a string. But if you use classes and objects you will have a different way to set callback. For example:
$a = function($callback, $arg) {
return call_user_func($callback, $arg));
}
// call a static method of the class with
// using fullname space and method name as a string
$a('Name\Space\MyClass::getInfo',5);
// call a static method of the class
// with using ::class
$a([MyClass::class, 'getInfo'], 5);
// call a method of an object
$myObject = new MyClass();
$a([$myOject, 'getInfo'], 5);
Three possibilities.
(1)
echo `__CLASS__`;
...returns namespace\classname as a string.
(2)
If you're trying to get the namespace\classname from another class, i.e., not the one where you're currently executing code, then I would suggest setting a public property inside each class such as:
public static $classname = __CLASS__;
which you could then access from anywhere as:
ClassName::$classname
Put it in each of your classes. Always use the same property name.
(3)
Have you considered the PHP function debug_backtrace() which returns a call stack with the most recent call at index = 0;
So, if:
$caller = debug_backtrace();
Then, $caller[0]['class'] contains the fully qualified class name, including any namespace, at the point where you called debug_backtrace().
I'm guessing that #2 is the solution that will work for you.
Just thought of a 4th possibility that doesn't depend on you adding any code to each class. Might add some overhead though, but so does my 3rd solution above.
(4)
$declared_classes = get_declared_classes();
This lists all of the classes currently declared within the PHP scope as fully qualified namespace\classname. You could search the returned array for partial string matches within the array and return the whole namespace\classname string.
One thing to keep in mind. You might have duplicates if different namespaces have same-named classes.
I've added this as a comment somewhere else but figured it might warrant an actual answer to this question. If you use:
$callback = [MyClass::class, 'myMethod'];
Then at least one IDE (PhpStorm) will recognize this as the callable that it is, allow you to navigate to it, mention it in "show usages" and automatically change it when it is renamed through a refactor. I use this in my code if, for instance, I reference a method in a test:
$this->mock(MyClass::class, function(MockInterface $mock) {
$mock->shouldReceive([MyClass:class, 'myMethod'][1])->andReturn(10);
});
Not the cleanest syntax, but it's workable.

PHP OOP find object by attribute

I'm trying to pull a random instance of a class (object) by generating a random number and checking object ids against that number.
I've found a lot of info on how to retrieve an object attribute (specifically here it's the id) but not without knowing which object first.
So within my class I've got
public function getID() {
return $this->id;
}
But
getID()
only works if I use it as
$object->getID()
Is there a way to do something similar but for every object in a class, without specifying the objects?
I'm trying to avoid having to write if/then for every object in the class.
You could set up an array of objects, then iterate over the array and call the getID() method on each object. If your array of objects is called $myObjects...
foreach($myObjects as $object) {
$object->getID(); //And do something with it
}
However, if you want to pick a random object out of a set of objects, testing a whole bunch of them to see if they are the object you picked isn't really ideal. You'd be better off putting them into an array and using array_rand() to select a random object out of the array.
What's your purpose for doing this? That may indicate a better way to approach this.
I think you'd have to have planned for this eventuality, then loop thru the candidate objects as #Surreal Dreams; suggests.
See Get all instances of a class in PHP

Hide columns when serializing via toArray()

I have a simple problem where I often return CRUD type Ajax requests with array serialized versions of Doctrine 1.2 models. I'd love to be able to simply return the toArray() method after the execute() result, however, this will display data about my models that I don't wish to expose. A simple example is on my user model the password and salt get displayed. While I realize those are already hashed values, it's something I'd rather not return as a JSON response.
I've poured over the Doctrine 1.2 manual, but did not find anything that offered the type of functionality I'm looking for. I realize I can iterate over the result to manually unset() the columns I wish to hide, but I'm hoping a more native solution is out there that I've overlooked.
Why don't you build your own toArray() ?
If you want to do that, you will have to extends the sfDoctrineRecord class that inherit from all Base* class. It is describe in the doc.
You have to put the configureDoctrine() inside config/ProjectConfiguration.class.php.
Then you will have a class like that:
class myDoctrineRecord extends sfDoctrineRecord
{
}
So you can easily add your custom toArray() here:
class myDoctrineRecord extends sfDoctrineRecord
{
public function toArray($deep = true, $prefixKey = false, array $excludeFields = array())
{
// do every thing like the original toArray
// but when a column match one entry in $excludeFields, don't add it
}
}
So, when using the toArray() method with an array of fields for the third parameters, they will be excluded from the result.

Using a Setter with Multidimensional Arrays

I'm trying to create a __set for an object in PHP that works with multidimensional arrays. Is this even possible?
I would like to be able to something like the following: $post->comments[0]['uid']=3;. However, comments is actually going to be a key in a private cache variable $_cache['comments']=array(). It'd be nice if the __set function could somehow get both the base key (comments) and the index (0) as well as the key/value it is setting (uid/3). However, that's not possible.
I've thought about making $_cache['comments'] and array of ArrayObjects but that wouldn't let me define a custom _get/_set overload. Instead, I think that I might end up having to create a new Comments object and then fill the array with those. However, I really wouldn't like to do this and it'd be sweet if somehow PHP could handle nested arrays in __set overloads.
I'm using Mongo and would like if I could just have one single object for each document. However, arrays objects in Mongo are creating a bit of a problem for me. I would like to just handle them as an array in PHP but that doesn't seem possible. The setter needs to take $post->comments[0]['uid']=3 and update both the cache as well as setting $this->data['comments'][0]['uid']=3.
I know that if comments was an array of objects I could do this:
$post->comments[0]->uid=3;
///Sets $_cache['comments'][0]->uid=3;
And it would work because the getter for comments would return the array of objects and allow it to access the uid property. I could then have a getter/setter within the comments object that would somehow edit the $post->data through a pseudo "friend" function/hack. However, I don't see an easy way of accomplishing this with arrays....
Any advice?
That's more complex than you actually imagine. You can accomplish what you want with a heap of workarounds, but it's seldomly worth the effort.
If ->comments itself is resolved by a getter method, than assigning something to the [0] subarray won't actually end up in the private property. And ->comments[0]= will not even invoke your setter method. Instead this is a read access.
To make this work at all you would have to make your __get method return an reference of & $this->_cache['comments'].
If you want to intercept set accesses in that comments array you would indeed need ArrayObject. The difference is that this requires to override offsetGet and offsetSet instead of __get and __set. But again, since you are accessing a further subarray, the __get method will actually be used and you need to return another reference, or yet again a level of ArrayObject workaround goo.
I jumped through some of these hoops when building my own PHP wrapper class.
https://github.com/gatesvp/MongoModel
It's still in the the works, but it does handle some basic "map this object to DB".
There's virtually nothing worthwhile written in PHP chat rooms or the php documentation that's going to be useful to you, Adam. Most of the suggestions tend along the lines of implementing interface ArrayAccess or extending class ArrayObject, both in the SPL. In fact, there is a surprisingly straightforward solution to your problem: $post->comments[0]['uid']=3 using overloaded setter __set().
Define private $comments = array(); in class post. For convenience, use a text key for the first subscript of $comments: here, integer 0 becomes, say, "zero". You then invoke the setter as follows:
$post->zero = ['uid', 3];
This invokes the magic setter because there is no publicly declared property $zero in class post: "The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope." (PHP 5 man page on Overloading.)
The setter can also be setComments(), a convenience because you won't have to discriminate among incoming properties to identify those intended for array comments, but the calling syntax becomes less natural.
Your overloaded, auto-magical function __set receives two arguments: a property and a value:
public function __set($property, $value) {
very reminiscent of Crockford's JSON protocol. It is helpful to think of it in those terms.
Since property "zero" that you sent in does not exist in classpost, it needs to be trapped, and my preferred method, since the first subscript in property comments will likely have several values, is to define a private array of supported subscript values in post:
private $indices = [
"zero" => 0,
"one" => 1,
"two" => 2,
"three" => 3
];
When the index for comments arrives in __set() as $property, it is verified to exist in $indices. Now you simply iterate through the array supplied in $value, extract
uid and its corresponding value, then assign to $comments as follows:
public function __set($property, $value) {
if (array_key_exists($property, $this->indices) && is_array($value))
foreach ($value as $uid => $uid_value)
$this->comments[$this->indices[property]][$uid] = $uid_value;
else
...
}
with $this->indices[property] being used to extract the integer value 0 to be used to
index the first dimension of comments, and $uid_value extracted with value int 3 to be assigned.
The approach outlined here is not a gimmick, workaround or clever trick. It's a straightforward design technique intended to work with one of SPL's facilities and can, in principle, be extended to arrays of arbitrary dimension. I have the design implemented in a production system so, if you're still having difficulty, post here and I'll help you to debug your application. Best of luck!
I believe the closest you can do for overloading some properties is to use the magic method __set() defined here: http://us.php.net/__set
I am not sure you can handle the [0] before it gets taken by the PHP compiler...
So your other solution would be to transform comments into a method
public function comments($id) {
return $this->obj[$id]; // Obj
}
And the object you return has the __set property
class Obj {
private $id;
public function __set($key, $value) {
if($key === 'uid') {
$_cache = $GLOBALS['_cache'];
$_cache['comments'][$this->id]->uid = $value;
}
}
}
There is a lot of code missing here, but you can figure out how to do it with this __set method()
Create a function instead of trying to hack it on top of something that isn't even meant for that.
public function setCommentUid($commentId, $uid) {
$this->_cache['comments'][$commentId]->uid = $uid;
}
//then...
$post->setCommentUid(0, 3);
This makes it much simpler to use the class and it's much easier to see what it does.

Php (eval vs call_user_func vs variable functions ...)

Even though there's some discussions regarding this issue I wanted to check on certain example what would be the best approach.
Instead of using existing solutions I created my own persistence layer (like many do)
So my approach is also in question here.
For every table in db I have model class that has appropriate getters and setters and some mandatory methods. I also created only one generic DAO class that handles all types of model objects.
So, for example to save any model object I instantiate genericDAO class and call save method that I pass model object as attribute.
Problem is that in runtime genericDAO class doesn't know whitch model object it gets and what methods (getters and setters) exist in it, so I need to call mandatory model class method that retrieves list of attributes as multiple string array.
For example for every attribute there's array(table_column_name,attribute_name,is_string).
When I call save function it looks like this:
public function save(&$VO) {
$paramArray = $VO->getParamArray();//get array of attributes
$paramIdArray = $paramArray[0]; //first attribute is always id
/*create and execute getId() and store value into $void to check if it's save or update*/
eval('$voId = $VO->get'.ucfirst($paramIdArray[1]).'();');
...
Currently I'm using eval to execute those methods, but as it is well known eval is very slow.
I'm thinking of changing that into call_user_func method
Something like:
$voId = call_user_func(array($VO, 'get'.ucfirst($paramIdArray[1])));
But also there's other solutions. I can maybe use something like this $method = 'get'.ucfirst($paramIdArray[1]));
$voId = $VO->$method();
or else
$method = 'get'.ucfirst($paramIdArray[1]));
$voId = $VO->{$method}();
What would be the best way?
First of all, there's no need to pass references like you are doing. You should give this a read to try to understand how PHP handles object references.
So public function save(&$VO) { should become public function save($VO) {.
Second, there is no need to use eval (in fact, it's better not to because of speed, debugability, etc). You can't stack-trace an eval call like you can a dynamic one.
Third, call_user_func is all but useless since PHP supports dynamic variable functions. Instead of call_user_func(array($obj, $method), $arg1), just call $obj->$foo($arg1). The call_user_func_array function is still useful since it supports variable length arguments and supports passing references.
So, ultimately, I would suggest this:
$method = 'get' . ucfirst($paramIdArray[1]);
$voId = $VO->$method();
Note that there's no need to call method_exists, since it may be callable and not exist due to __get magic method support...
I normally would use:
$method = 'get'.ucfirst($attribute);
if(method_exists($obj, $method){
$obj->$method();
}
But unless there is a very good reason i would just return a key => value array from getParamArray. And operate on that instead of using the getters...

Categories