json_encode($myObject) dont want reveal my whole object - php

Our PHP application makes use of json_encode($myObject) a lot, in conjunction with the mustache template library. It's awesome.
Trouble is, when returning json data from an ajax request it reveals the whole stucture of our objects, even if we don't have data assigned to them. A simple example:
Fetch a user via ajax and let the server return the object with json_encode($user)
The json:
"_userID":"1","_password":null,"_avatar":"0000723_8949d1d7eb0214fdf6c1af3cb6b66ed3_a.jpg","_blocked":null,"_confirmed":null,"_registerDate":null,"_lastVisitDate":null,"_template":null,"_referrerUserID":null,"_referrerURL":null,"_referrerDomain":null,"_referrerSearchTerms":null,"_confirmationHash":null,"_type":"Administrator" and so on...
It reveals a lot about our objects when all I wanted to return was just a few fields.
Obviously I could rewrite our server side code to send back an array or different objects which are more limited but really that makes life harder and sort of prevents our clean template design which handles the same objects as our server does.
How do I clear all null properties from a json_encode. Does anybody else have this issue and a nice and clean solution?

You should probably adapt your server side code to ignore the null values and return only the fields that are set (thus avoiding unnecessary bandwidth usage).
In your clientside code I suggest you have a set of defaults for your template and extend them received JSON with the defaults.
I'd you're using jquery, the code would look like this :
var defaults ={someDay:"somePlace"};
var object = $.extend({},defaults,theJson);
update
and in order to "clean up" the object in php, you can do something like :
foreach($obj as $k => $v)
if($v == null)
unset($obj[$k]);

From my experience when dealing with objects and JSON I do not think there is a way without iterating over each value. I always find it better to have a _toJson method implemented within the class, and in that do all the necessary preparations before encoding it to JSON (utf8-encoding issues, use getters instead of calling variables directly etc).

Thanks to #gion_13, i've adapted his code and come up with a full solution:
$output = array('data'=>$data,'template'=>$template);
$output = object_unset_nulls($output);
echo json_encode($output);
function object_unset_nulls($obj)
{
$arrObj = is_object($obj) ? get_object_vars($obj) : $obj;
foreach($arrObj as $key => $val)
{
$val = (is_array($val) || is_object($val)) ? object_unset_nulls($val) : $val;
if (is_array($obj))
$obj[$key] = $val;
else
$obj->$key = $val;
if($val == null)
unset($obj->$key);
}
return $obj;
}

Related

PHP - Reference a StdClass Object from a variable?

Maybe I am searching wrong, but I'm unable to find how to specify an Object Class in a string. Example:
I keep a list of json sites in a database, perform a foreach loop to retrieve a specific item from each site. However, each site has a different structure.
SITE 1: $result->Food->Price->Today;
SITE 2: $result->Pizza->Slice->discount;
I am trying to keep "$result->Pizza->Slice->discount" in a variable (specified in database) and return it from the class.
Sounds easy, but I'm new to class objects and all I find is how to create an object from an array.
Store this value into your database:
serialize(['Pizza', 'Slice', 'discount']);
When reading the value, unserialize it:
unserialize($value_from_db);
To retrieve the value from the JSON object use this simple function:
function retrieve_value($object, $trail) {
foreach ($trail as $name)
if (isset($object->$name))
$object = $object->$name;
else
throw new Exception("Object does not have the property $name");
return $object;
}
So you have something like this:
$value = retrieve_value(json_decode($json), unserialize($db_value));
Do not use eval(), because it is evil.
A dirty way to do this would be to json encode and decode.
$array = array([Your array]);
$obj = json_decode(json_encode($array));
Haven't tested the code though.
I guess you could store the pathing inside the database then use "eval()" with the path on the object. Just make sure you know no one can alter the pathing because eval is dangerous with un-sanitized code!
eval("\$value = \$result->{$pathvar};");
I didn't test that, but something of the sort. Of course $pathvar would be the value of the path coming from the database, whatever variable that's stored in.

How to make Backbone's and MongoDB's ids work seamlessly?

My Backbone app is communicating with a REST API built on top of MongoDB, so my objects' "natural" ids are really MongoIDs. When serialized into JSON, they look like:
"_id":{"$id":"505099e998dee4db11000001"}
The Backbone documentation mentions that you can specify another name than id for your Backbone model's id attribute (using idAttribute), however, as MongoIDs' string representations are nested, just using idAttribute: '_id' doesn't help in consuming JSON directly.
Is there a way around this, other than rewriting attributes on the server side?
Update:
Here's what I'm doing server-side:
$m = new Mongo();
$posts = $m->mydatabase->posts->find();
$out = array();
foreach ($posts as $post) {
$out[] = $post;
}
echo json_encode($out);
I know I could do something like $post['id'] = (string) $post['_id']; unset($post['_id']); but it is precisely what I'm looking to avoid!
This sounds like a good example for parse().
Since your Mongo JSON ends up sending:
"_id":{"$id":"505099e998dee4db11000001"}
Why not use the Backbone parse to properly namespace the incoming data?
parse: function(response) {
response.id = response._id['$id'];
delete response._id;
return response;
}
Or something like this. Similarly, since Backbone will be sending the id as JSON with 'id', your server might take that and "translate" it the other way.
If you want to use a different id attribute, _id, you would just replace the above parse with this code:
idAttribute: '_id',
parse: function(response) {
response._id = response._id['$id'];
return response;
}
Whatever works best for you. :-)
It's better to do the conversion in the browser so you move the computation off from the server. So you'd want to create a base MongoModel, and then you can extend from MongoModel instead of Backbone.Model.
MongoModel = Backbone.Model.extend({
// this handle conversion between the mongo's _id and backbone id
// this is best done on the client side to alleviate server load
//_id : { "$oid" : ... } <--> id
parse : function (response) {
response.id = response._id.$oid;
delete response._id;
return response;
},
toJSON : function () {
var
json = Backbone.Model.prototype.toJSON.apply(this);
json._id = {'$oid':json.id};
delete json.id;
return json;
}
});
Looking at various suggested options I found the easiest would be to do what you want to avoid. (Using parse does work though)
$m = new Mongo();
$posts = $m->mydatabase->posts->find();
$out = array();
foreach ($posts as $post) {
// populate an id field
$post['id']=$post['_id']->{'$id'},
$out[] = $post;
}
echo json_encode($out);
Keeping $post['_id'] if you want your model to send it back when syncing.
I prefer correcting the API as more clients might want to connect to it.
In Ruby I'm able to do the following:
items.find.to_a.map do |item|
frombsonid(item)
end.to_json
def frombsonid(obj) obj.merge({'_id' => obj['_id'].to_s}) end
and when you get the data back, you can convert the string to BSON::ObjectId
def tobsonid(id) BSON::ObjectId.fromstring(id) end
So the idea is to replace _id in each item with a string version of BSON::ObjectId
In backbone you can add
idAttribute: "_id",
In PHP you will use different syntax and methods, but I believe it's quite possible to replicate this.
Your PHP code is the REST API that Backbone will interact with, so it should handle the abstraction of Mongo's complex _id property to the simple id string you're looking for. That is definitely the API's job.
Backbone, or any other consumer of your API, should NOT know anything about Mongo or _id.
Why?
Because if you were to switch from Mongo to some other Db where there is no _id, then your API is still good because all that was ever exposed was id, which is super generic. So you're original idea was actually the best idea (although you'd want to abstract that piece of code into something that can be reused for all models, not just posts).

How to store data that includes callbacks

There are, of course, many many ways to store a base of data. A database being the most obvious of them. But others include JSON, XML, and so on.
The probem I have with the project I'm working on right now is that the data being stored includes callback functions as part of the objects. Functions cannot be serialised, to my knowledge. So, what should I do?
Is it acceptable to store this data as a PHP file to be included? If so, should I create one big file with everything in, or divide it into separate files for each "row" of the database?
Are there any alternatives that may be better?
Depending on how elaborate you callbacks are, for serializing you could wrap them in all in a class which utilizes some __sleep (create callback representation) & __wakeup (restore callback) voodoo, with an __invoke() method calling the actual callback. Assuming you can reverse engineer / recreate those callbacks (i.e. object to point to is clear).... If not, you are probably out of luck.
Well if they are created by the developer it should be easy to come up with a format... for example with JSON:
{
"callback" : {
"contextType": "instance", // or "static"
"callable" : "phpFunctionName",
"arguments" : []
}
}
So then on models the would be able to use this feature you might do something like:
protected function invokeCallback($json, $context = null) {
$data = json_decode($json, true);
if(isset($data['callaback'])) {
if($data['contextType'] == 'instance') {
$context = is_object($context) ? $context : $this;
$callable = array($context, $data['callable']);
} else {
// data[callable] is already the string function name or a array('class', 'staticMethod')
$callable = $data['callable'];
}
if(is_callable($callable) {
return call_user_func_array($callable, $data['arguments'];
} else {
throw new Exception('Illegal callable');
}
}
return false;
}
Theres some more error handling that needs to happen in there as well as some screening of the callables you want to allow but you get the idea.
Store names of callbacks instead, have a lookup table from names to functions, use table.call(obj, name, ...) or table.apply(obj, name, ...) to invoke. Do not have any code in the serialisable objects themselves. Since they are developer-created, there should be a limited inventory of them, they should be in code, and should not be serialised.
Alternately, make a prototype implementing the callbacks, and assign it to obj.__proto__. Kill the obj.__proto__ just before you serialise - all the functions disappear. Restore it afterwards. Not sure if cross-browser compatible; I seem to have read something about __proto__ not being accessible in some browsers khminternetexploreroperakhm

PHP Serialization Alternative

I'm looking for a good cache key for APC that represents some complied information about an object, using the "object" as the key. I have a compilation method that does something like this:
function compile(Obj $obj)
{
if ($this->cache)
{
$cachekey = serialize($obj);
if ($data = $this->cache->get($obj))
{
return $data
}
}
// compute result here
if ($this->cache)
{
$this->cache->set($cachekey, $result);
}
return $result;
}
If it's not obvious, $this->cache is an implementation of an interface with the methods get and set.
Is there a quicker alternative to creating a key that's unique to some of the properties of this object? I can extract the relevant bits out, but then they are still arrays, which would have the same problem with serialization that I had with the objects in the first place.
Serialize works, from a "correctness" position, but it seems wasteful (both in size of outputted key, and in computational complexity).
EDIT: I would also like to add, if it's not obvious, that I will not be needing to unserialize this object. My verbatim code for the current cache key is actually:
$cachekey = 'compile.' . sha1(serialize($obj));.
EDIT 2: The object I'm working with has the following definition:
class Route
{
protected $pattern;
protected $defaults = array();
protected $requirements = array();
}
Pattern and requirements are the values of the object that will change the output of this method, therefore a hash of these values must be present in the cache key.
Also, someone suggested uniqid(), which would defeat the purpose of a general cache lookup key, as you could not reliably regenerate the same ID from the same information.
EDIT 3: I guess I'm not giving enough context. Here's a link to the code so far:
https://github.com/efritz/minuet/blob/master/src/Minuet/Routing/Router.php#L160
I guess I'm really only trying to avoid expensive calls to serialize (and I guess sha1, which is also a bit expensive). It's possible that the best I can do is try to reduce the size of what I'm serializing...
One way to do it might be to generate a key based simply from the values you use to compute the result..
Here is a rough example.
function compile(Obj $obj)
{
if ($this->cache)
{
$cachekey = 'Obj-result-' . sha1($obj->pattern . '-' . serialize($obj->requirements));
// You could even try print_r($obj->requirements, true)
// or even json_encode($obj->requirements)
// or implode('-', $obj->requirements)
// Can't say for sure which is slowest, or fastest.
if ($data = $this->cache->get($cachekey))
{
return $data
}
}
// compute result here
$result = $obj->x + $obj->y; // irrelevant, and from original answer.
if ($this->cache)
{
$this->cache->set($cachekey, $result);
}
return $result;
}
Since you use an array of data, you'd still need to turn it into something that makes sense as a key.. However this way you're now only serializing a part of the object, rather then the whole thing. See how it goes. :)
I would suggest the spl_object_hash function that seems to fit perfectly for your needs.
Actually it is very hard to suggest any viable solution without knowing how the whole system works.
But, Why don't you just simply add a cache_key property with a uniqid() value in your object?

Working with PHP objects

I was previously mostly scripting in PHP and now considering getting "more serious" about it :)
I am working on a hiking website, and I needed to put some values into an object that I then try to pass back to the calling code.
I tried doing this:
$trailhead = new Object ();
But the system sort of barfed at me.
Then I didn't declare the object at all, and started using it like this:
$trailhead->trailhead_name = $row['trailhead_name'];
$trailhead->park_id = $row['park_id'];
That seemed to work reasonably ok. But there are at least 3 problems with this:
Since that code gets executed when getting things from the database, what do I do in case there is more than one row?
When I passed the $trailhead back to the calling code, the variables were empty
I actually am maybe better off making a real object for Trailhead like this:
class Trailhead
{
private $trailhead_name;
private $park_id;
private $trailhead_id;
public function __construct()
{
$this->trailhead_name = NULL;
$this->park_id = NULL;
$this->trailhead_id = NULL;
}
}
What do people generally do in these situations and where am I going wrong in my approach? I know its more than one place :)
$trailheads[] = $trailhead;
I'd do a print_r() of $trailhead to check that it's what you expect it to be. The default object type in PHP is going to be stdClass.
Yes, that's going to be better, as it'll allow your Trailhead objects to have functions. The way you're currently doing it is basically taking advantage of none of PHP's object functionality - it's essentially an array with slightly different syntax.
I think you should get in "contact" with some of the basics first. Objects in PHP have sort of a history. They are a relative to the array and there are two sorts of objects:
data-objects
class objects
data objects are called stdClass and that's actually what you were initially looking for:
$trailhead = new Object();
in PHP is written as:
$trailhead = new stdClass;
(with or without brackets at the end, both works)
You then can dynamically add members to it, like you did in the second part without declaring the variable (that works, too in PHP):
$trailhead->trailhead_name = $row['trailhead_name'];
$trailhead->park_id = $row['park_id'];
If you want to more quickly turn $row into an object, just cast it:
$trailhead = (object) $row;
That works the other way, too:
$array = (array) $trailhead;
As you can see, those basic data based objects in PHP do not hide their relationship to the array data type. In fact you can even iterator over them:
foreach($trailhead as $key=>$value) {
echo $key, ' is ', $value, "<br>\n";
}
You can find lots of information about this in the PHP manual, it's a bit spread around, but worth to know the little basics before repeating a lot of names only to pass along the data that belongs together.
Next to these more or less stupid data objects, you can code complete classes that - next to what every stdClass can do - can have code, visibility, inheritance and all the things you can build nice stuff from - but this can be pretty complex as the topic is larger.
So it always depends on what you need. However, both type of objects are "real objects" and both should work.
class myClass {
function importArray(array $array) {
foreach($array as $key=>$value) {
if(!is_numeric($key)) $this->$key=$value;
}
}
function listMembers() {
foreach($this as $key=>$value) {
echo $key, ' is ', $value, "<br>\n";
}
}
}
$trailhead = new myClass();
$trailhead->importArray($row);
echo $trailhead->park_id;
Keep in mind that instead of creating a set of objects that merely does the same in each object (store data), you should just take one flexible class that is handling the job flexible (e.g. stdClass) because that will keep your code more clean.
Instead of coding a getter/setter orgy you can then spend the time thinking about how you can make the database layer more modular etc. .
Just pass back an array:
$trailhead = array(
'trailhead_name' => $row['trailhead_name'],
'park_id' => $row['park_id'],
'trailhead_id' => $row['trailhead_id'],
)
Then either access it like:
$trailhead['park_id'];
or use the nifty list() to read it into variables:
list($trailhead_name, $park_id, $trailhead_id) = $trailhead;

Categories