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

5

10

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.
Seanseana answered 25/2, 2014 at 16:6 Comment(0)
S
5

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);
Stalemate answered 15/3, 2014 at 10:6 Comment(1)
Thanks for your answer and sorry for the delay. I considered using resources for API calls and I do that on other projects. I had my own reasons to use regular controllers in this project. Your solution requires that I write a Javascript routine that will convert between JSON represented Carbon objects to JS Date objects and back. I did that, but I was expecting to find a better bundled-into-Laravel solution since this behavior looks quite standard to me.Seanseana
H
5

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();
    }
}
Hamstring answered 25/8, 2014 at 8:45 Comment(0)
A
1

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.

Ashok answered 15/3, 2014 at 3:27 Comment(0)
N
1

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

Nazareth answered 18/3, 2014 at 15:9 Comment(0)
Q
0

If you want to get date column of your model (created_at) in string format use like this:

$response['created_at'] = Carbon::parse($model->created_at)->toDateString();

this will change

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

into this:

created_at = "2014-02-25 12:55:29"

Quartz answered 30/6, 2016 at 22:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.