Mauro Mauro - 1 year ago 100
JSON Question

How to handle JSON responses for models that include Carbon dates on Laravel?

I'm writing a pretty simple app that requiers Backbone.js models and Laravel 4 models to be in sync. Trouble arises when I the Laravel models involve Carbon dates. My Laravel controller looks like this:

class OrderController extends \BaseController {
public function update($id = null) {
if (Request::ajax())
return $order;

This successfully responds with a JSON representation of $order which the client side uses to stay in sync. However, Carbon dates are returned as the Carbon object representation, like this:

"delivered_at":{"date":"2014-02-25 12:55:29","timezone_type":3,"timezone":"America\/Argentina\/Buenos_Aires"}

I could manage to interpret this as a javascript Date object pretty easily, however, when this object goes back to laravel, JSON removes the
class and Eloquent fails to read that as a date:

[2014-02-25 12:58:32] log.ERROR: exception 'ErrorException' with message 'preg_match() expects parameter 2 to be string, array given' in vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2210
Stack trace:
#0 [internal function]: Illuminate\Exception\Handler->handleError(2, 'preg_match() ex...', '/Users/maurospi...', 2210, Array)
#1 vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(2210): preg_match('/^(\d{4})-(\d{2...', Array)
#2 vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(2151): Illuminate\Database\Eloquent\Model->fromDateTime(Array)
#3 vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php(306): Illuminate\Database\Eloquent\Model->setAttribute('delivered_at', Array)
#4 app/controllers/OrderController.php(120): Illuminate\Database\Eloquent\Model->fill(Array)
#5 [internal function]: OrderController->update('91')
#6 vendor/laravel/framework/src/Illuminate/Routing/Controllers/Controller.php(138): call_user_func_array(Array, Array)
#7 vendor/laravel/framework/src/Illuminate/Routing/Controllers/Controller.php(115): Illuminate\Routing\Controllers\Controller->callMethod('update', Array)
#8 vendor/laravel/framework/src/Illuminate/Routing/Router.php(985): Illuminate\Routing\Controllers\Controller->callAction(Object(Illuminate\Foundation\Application), Object(Illuminate\Routing\Router), 'update', Array)
#9 [internal function]: Illuminate\Routing\{closure}('91')
#10 vendor/laravel/framework/src/Illuminate/Routing/Route.php(80): call_user_func_array(Object(Closure), Array)
#11 vendor/laravel/framework/src/Illuminate/Routing/Route.php(47): Illuminate\Routing\Route->callCallable()
#12 vendor/laravel/framework/src/Illuminate/Routing/Router.php(1016): Illuminate\Routing\Route->run(Object(Illuminate\Http\Request))
#13 vendor/laravel/framework/src/Illuminate/Foundation/Application.php(574): Illuminate\Routing\Router->dispatch(Object(Illuminate\Http\Request))
#14 vendor/laravel/framework/src/Illuminate/Foundation/Application.php(550): Illuminate\Foundation\Application->dispatch(Object(Illuminate\Http\Request))
#15 public/index.php(49): Illuminate\Foundation\Application->run()
#16 {main} [] []

So I either need to:

  1. Extend the JsonResponse class to convert Carbon dates to string representations.

  2. Extend the Eloquent class to interpret StdClass objects of the
    class structure to dates.

  3. Do something that I'm clearly missing, Laravel 4 claims to be awesome at REST so I guess I'm missing something.

Answer Source

First, I suggest that you separate API from controllers. Use resources for API calls.

For the object returned to Laravel, I don't know how are you processing it to get the error, but you should initiate a new Carbon instance if you want a Carbon date. Else you could just return the date as a string, Laravel's Model will handle the rest.

Assuming the object returned is:

    "delivered_at":{"date":"2014-02-25 12:55:29","timezone_type":3,"timezone":"America\/Argentina\/Buenos_Aires"}

And the variable $data will have the current response, you could simply overwrite delivered_at:

$data->delivered_at = $data->delivered_at->date;

Or if you want a Carbon object:

$data->delivered_at = new \Carbon\Carbon($data->delivered_at->date, $data->delivered_at->timezone);