Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 Carbon 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 Carbon 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.
like image 414
Mauro Avatar asked Feb 25 '14 16:02

Mauro


3 Answers

This might come in a bit late, but I usually make use of accessors and mutators to achieve this. For example, if I want all created_at and updated_at fields always to be returned in the ATOM format, I create a base model class extending Eloquent which every other model inherits:

use Carbon\Carbon as Carbon;
use Illuminate\Database\Eloquent\Model as Model;

class BaseModel extends Model {

    public function getCreatedAtAttribute($value)
    {
        return Carbon::parse($value)->toATOMString();
    }

    public function setCreatedAtAttribute($value)
    {
        $this->attributes['created_at'] = Carbon::parse($value)->toDateTimeString();
    }

    public function getUpdatedAtAttribute($value)
    {
        return Carbon::parse($value)->toATOMString();
    }

    public function setUpdatedAtAttribute($value)
    {
        $this->attributes['created_at'] = Carbon::parse($value)->toDateTimeString();
    }
}
like image 81
cafonso Avatar answered Oct 16 '22 18:10

cafonso


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);
like image 43
Blue Genie Avatar answered Oct 16 '22 18:10

Blue Genie


This may not be the same but I would get this error when working with timestamps and carbon but using strtotime() on the data i was passing resolved my issue, may help you.

like image 43
Mark Avatar answered Oct 16 '22 17:10

Mark