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

11,802

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

How you handle dates in both backbone and Laravel will have an impact.
You need to pick one date format and stick to it. And then ensure that both sides convert to that format when passing data back and forward to the JS and the Controllers.

If you send a pure JavasScript Date object then it returns a date string that looks like this
"Sat Apr 19 2014 00:00:00 GMT+0200 (South Africa Standard Time)"
Which is not so nice, since PHP's strtotime ends up parsing it a funky.

here is an example:

$jsdate = "Sat Apr 19 2014 00:00:00 GMT+0200 (South Africa Standard Time)";
$carbon = Carbon::createFromTimestamp(strtotime($jsdate));
$iso8601 = $carbon ->format(Carbon::ISO8601)
//output '1970-01-01T02:00:00+0200' which is a UNIX timestamp 0.

Why that date? maybe somebody else can elaborate better then I can. You can use a custom date format to read it properly, but rather use a format both can understand.

Like ISO8601

//javascript
var jsdate = (new Date()).toISOString();

And in php Carbon should be able to handle it without issues

Share:
11,802
Mauro
Author by

Mauro

Updated on July 25, 2022

Comments

  • Mauro
    Mauro almost 2 years

    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.