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

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).

Related

Symfony2 + AngularJS

I've been developing a web app using symfony2 and now I added some Angularjs.
I have an input field, where you can filter products by name, the problem is the following, I've a controller in php, I do some queries, and then I render the view, passing the parameters, in this case I do something like this,
.......
return $this->render('default/index.html.twig',array( 'products' => $products));
My question is, if I wanted to filter those products by name using angular, how can I accomplish that? (I wanted something like phonecat-app in the official angular tutorial, where you can filter by name)
So far I've done this:
var StoreApp = angular.module('StoreApp', []);
StoreApp.controller('StoreCtrl', function($scope,$http){
$http.get('http://localhost:8000').success(function(data){
$scope.stores = data;
});
});
The problem is that I don't know which URL to put in the GET parameter, I've several tables in the database, and I don't know how to address them.
I'm using a local web server on port 8000, and Doctrine.
In order to filter the data on the client side using angular, you need to get the data from your symfony2 application into the angular scope with javascript.
There are multiple ways of doing this, the quick and dirty way is to render the products array from php directly in the angular ng-init attribute as explained in https://stackoverflow.com/a/28508012/1016372 .
In my opinion the best way to get the data into your angular application is creating a "RESTful endpoint" that exposes your product data in JSON format to your angular application. Using symfony2 you could create a controller that returns the product data if you make a query for http://localhost:8000/products with a controller similar to this snippet:
class ProductController
{
public function getProductsAction()
{
$products = $this->getRepository('Products')->findAll(),
return new JsonResponse($products);
}
}
Finally I could solve the problem.
Here is my controller,
public function getAllStoresAction()
{
$em = $this->getDoctrine()->getEntityManager();
$query = $em->createQuery('SELECT s FROM AppBundle\Entity\Store s');
$result = $query->getArrayResult();
return new Response(json_encode($result), 200);
}
then my app.js
var storeApp = angular.module('storeApp', []).config(function($interpolateProvider){
$interpolateProvider.startSymbol('{[{').endSymbol('}]}');
});
storeApp.controller('StoreCtrl',function($scope,$http){
$http.get('/get-all-stores').success(function(data){
$scope.stores = data;
});
$scope.orderProp = 'name';
});
and my routing.yml
get_all_stores:
path: /get-all-stores
defaults: { _controller: AppBundle:Default:getAllStores}
I managed to get the data back, but the problem was that it returned me an empty array.
Searching a bit, I found out that I wasn't serializing the data, php does not do that automatically, so I used this getArrayResult(); and it worked fine!
Thanks for the help Peter Peerdeman

Instantiate an stdClass object from json schema specification in PHP

I have a set of JSON requests that I must send to a RESTFul API in order to get some response objects, you know, the usual thing for a webapp, however these API request objects are properly documented with a json schema specification for each, so I would like to load those schema files and create stdClass object instances based on that info automagically.
Is there some way to do this with a library or something in PHP? (don't want to reinvent the wheel)
Thanks!
Edit: Have a look at this schema file which contains an example of what I want to load and build object instances from.
Disclaimer: I do know json_encode / json_decode which is not what I'm looking for. Using that I'd need to traverse through the returned schema object and then create another object/array based on the schema read, which is not what I want.
I don't think there's a built-in way of doing this, but it should be relatively trivial to implement:
function createObj( $json ) {
$obj_schema = json_decode($json, true);
$new_obj = new StdClass;
foreach($obj_schema['properties'] as $property) {
$new_obj->{$property} = null;
}
return $new_obj;
}

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

json_encode($myObject) dont want reveal my whole object

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;
}

PHP, MYSQL & JSON

Ok, So I'm using AJAX to pull some information from my MYSQL database and put it onto the screen. The thing is I cannot understand JSON for the life of me. Can you suggest any tutorials or anything that will help?
I mean I get that I can encode my query via JSON but I guess it's the javascript side I do not understand.
Here's my quick tutorial:
JSON is a means for expressing data for arrays, objects, and their contents. It has nothing to do with object behaviour (methods).
<?php
class Test {
public $hello = 'hello';
public $something = array('hello1', 'hello2');
public __construct() {
}
public void printHello() {
echo $this->hello;
}
}
?>
This class would in JSON would look like:
var obj = {
"hello": "hello",
"something": ["hello1", "hello2"]
};
As you can see, JSON is similar to maps in a lot of languages (key/value pairs). You can also see, that only data is represented. JSON is also shorthand for JavaScript builtins. For example, this previous object can be written in JavaScript like so.
var obj = new Object();
obj.hello = "hello";
obj.something = new Array("hello1", "hello2");
Hope this gives you a little idea of what JSON is about.
First, read this: http://www.json.org/js.html
Then, practice with this: http://jsonlint.com/
You can read about using JSON in JavaScript at the Mozilla docs.
Maybe this short example will help you: http://www.factsandpeople.com/facts-mainmenu-5/26-html-and-javascript/89-jquery-ajax-json-and-php.
jQuery docs - getJSON()
How to use JSON (updated with example)
http://www.javascriptkata.com/2009/09/16/how-to-use-json-updated-with-example/

Categories