Laravel REST API Type Preservation (Ex. how to prevent ints being sent as strings)

Asked
Active3 hr before
Viewed126 times

7 Answers

90%

It is possible to convert integer and float columns back to PHP numbers by setting the MYSQLI_OPT_INT_AND_FLOAT_NATIVE connection option, if using the mysqlnd library. If set, the mysqlnd library will check the result set meta data column types and convert numeric SQL columns to PHP numbers, if the PHP data type value range allows for it. This way, for example, SQL INT columns are returned as integers., Excellent solution. I like that this solves the issue regardless of underlying platform. Here's a quick link to the docs. Great stuff here: laravel.com/docs/5.4/eloquent-mutators#attribute-casting – TomWilsonFL Feb 3 '17 at 17:51 , How does the Bladesinging wizard's Extra Attack feature interact with the additional Attack action from the Haste spell? ,I just ran into this same issue! For those of you looking for a more appropriate solution, you might want to check out the $casts property for Eloquent models.

The accepted solution will work, but it will also convert fields that you may not want converted. I would recommend adding this to your Eloquent model:

protected $casts = ['imdb_rating' => 'float', 'imdb_votes' => 'integer'];
load more v
88%

Before diving into all of the options available to you when writing resources, let's first take a high-level look at how resources are used within Laravel. A resource class represents a single model that needs to be transformed into a JSON structure. For example, here is a simple UserResource resource class:,Every resource class defines a toArray method which returns the array of attributes that should be converted to JSON when the resource is returned as a response from a route or controller method.,In this example, if the relationship has not been loaded, the posts key will be removed from the resource response before it is sent to the client.,Once the resource collection class has been generated, you may easily define any meta data that should be included with the response:

To generate a resource class, you may use the make:resource Artisan command. By default, resources will be placed in the app/Http/Resources directory of your application. Resources extend the Illuminate\Http\Resources\Json\JsonResource class:

php artisan make: resource UserResource
load more v
72%

Laravel is an opinionated PHP framework. It abstracts away the minutiae of building a web application to facilitate productivity, maintenance, and forward compatibility.,With the rise of mobile development and JavaScript frameworks, using a RESTful API is the best option to build a single interface between your data and your client.,Laravel development has certainly improved my experience with PHP and the ease of testing with it has solidified my interest in the framework. It’s not perfect, but it’s flexible enough to let you work around its issues.,With Laravel installed, you should be able to start the server and test if everything is working:

As with all modern PHP frameworks, we’ll need Composer to install and handle our dependencies. After you follow the download instructions (and add to your path environment variable), install Laravel using the command:

$ composer global require laravel / installer
load more v
65%

All string data must be UTF-8 encoded. , PHP implements a superset of JSON as specified in the original » RFC 7159. , The value being encoded. Can be any type except a resource. , json_​encode

{
   "a": 1,
   "b": 2,
   "c": 3,
   "d": 4,
   "e": 5
}
load more v
75%

When creating APIs, we often need to work with database results to filter, interpret or format values that will be returned in the API response. API resource classes allow you to convert your models and model collections into JSON, working as a data transformation layer between the database and the controllers.,If we want to be more specific in terms of what album attributes will be present in the response, we can create an AlbumResource similar to what we did with songs.,Every now and then, we might have a conditional determining the type of response that will be returned.,Here, we first create a song resource then call jsonSerialize() on the SongResource to transform the resource into JSON format, as that’s what will be sent to our frontend.

First, clone the repo:

git clone `git@github.com:do-community/songs-demo.git`

Then, navigate to the project folder:

cd songs - demo

Create a .env file by running the following command:

cp.env.example.env

Install the packages and dependencies:

composer install

Then, generate an encryption key for the app:

php artisan key: generate

Run migrations and seed database with some sample data:

php artisan migrate: refresh--seed

Let’s start by generating a SongResource class:

php artisan make: resource SongResource
[...]
class SongResource extends JsonResource {
   /**
    * Transform the resource into an array.
    *
    *  @param  \Illuminate\Http\Request  $request
    *  @return array
    **/
   public
   function toArray($request) {
      return parent::toArray($request);
   }
}
[...]
public
function toArray($request) {
   return [
      'id' => $this - > id,
      'title' => $this - > title,
      'rating' => $this - > rating,
   ];
}
[...]
use App\ Http\ Resources\ SongResource;
use App\ Song;
[...]

Route::get('/songs/{song}', function(Song $song) {
   return new SongResource($song);
});

Route::get('/songs', function() {
   return new SongResource(Song::all());
});

If we visit the URL /api/songs/1, we’ll see a JSON response containing the key-value pairs we specified in the SongResource class for the song with an id of 1:

{
   data: {
      id: 1,
      title: "Mouse.",
      rating: 3
   }
}

However, if we try visiting the URL /api/songs, an Exception is thrown:

OutputProperty[id] does not exist on this collection instance.

If we wanted a collection returned instead of a single resource, there is a static collection() method that can be called on a Resource class passing in a collection as the argument. Let’s update our /songs route closure to this:

Route::get('/songs', function() {
   return SongResource::collection(Song::all());
});

Visiting the /api/songs URL again will give us a JSON response containing all the songs.

{
   "data": [{
         "id": 1,
         "title": "Mouse.",
         "rating": 3
      },
      {
         "id": 2,
         "title": "I'll.",
         "rating": 0
      }
   ]
}

To generate a collection class, we run:

php artisan make: resource SongsCollection
[...]
class SongsCollection extends ResourceCollection {
   public
   function toArray($request) {
      return [
         'data' => $this - > collection,
         'meta' => ['song_count' => $this - > collection - > count()],
      ];
   }
}
[...]
use App\ Http\ Resources\ SongsCollection;
[...]
Route::get('/songs', function() {
   return new SongsCollection(Song::all());
});

And visit the URL /api/songs, we now see all the songs inside the data attribute as well as the total count inside the meta bit:

{
   "data": [{
         "id": 1,
         "title": "Mouse.",
         "artist": "Carlos Streich",
         "rating": 3,
         "created_at": "2018-09-13 15:43:42",
         "updated_at": "2018-09-13 15:43:42"
      },
      {
         "id": 2,
         "title": "I'll.",
         "artist": "Kelton Nikolaus",
         "rating": 0,
         "created_at": "2018-09-13 15:43:42",
         "updated_at": "2018-09-13 15:43:42"
      },
      {
         "id": 3,
         "title": "Gryphon.",
         "artist": "Tristin Veum",
         "rating": 3,
         "created_at": "2018-09-13 15:43:42",
         "updated_at": "2018-09-13 15:43:42"
      }
   ],
   "meta": {
      "song_count": 3
   }
}
[...]
public
function toArray($request) {
   return [
      'data' => SongResource::collection($this - > collection),
      'meta' => ['song_count' => $this - > collection - > count()]
   ];
}
[...]
Route::get('/songs/{song}', function(Song $song) {
   return (new SongResource(Song::find(1))) - > additional([
      'meta' => [
         'anything' => 'Some Value'
      ]
   ]);
});

In this case, the response would look somewhat like this:

{
   "data": {
      "id": 1,
      "title": "Mouse.",
      "rating": 3
   },
   "meta": {
      "anything": "Some Value"
   }
}
[...]
class SongResource extends JsonResource {
   public
   function toArray($request) {
      return [
         [...]
         // other attributes
         'album' => $this - > album
      ];
   }
}

To create the AlbumResource, run:

php artisan make: resource AlbumResource
[...]
class AlbumResource extends JsonResource {
   public
   function toArray($request) {
      return [
         'title' => $this - > title
      ];
   }
}
[...]
class SongResource extends JsonResource {
   public
   function toArray($request) {
      return [
         [...]
         // other attributes
         'album' => new AlbumResource($this - > album)
      ];
   }
}
[...]
DB::listen(function($query) {
   var_dump($query - > sql);
});
[...]
return new SongsCollection(Song::with('album') - > get());
public
function toArray($request) {
   return [
      [...]
      // other attributes
      'songs' => SongResource::collection($this - > whenLoaded($this - > songs))
   ];
}
public
function toArray($request) {
   return [
      [...]
      // other attributes
      'songs' => SongResource::collection($this - > whenLoaded($this - > songs)),
      $this - > mergeWhen($this - > songs - > count > 10, ['new_attribute' => 'attribute value'])
   ];
}

Let’s create the test:

php artisan make: test SongResourceTest--unit
[...]
use App\ Http\ Resources\ SongResource;
use App\ Http\ Resources\ AlbumResource;
[...]
class SongResourceTest extends TestCase {
   use RefreshDatabase;
   public
   function testCorrectDataIsReturnedInResponse() {
      $resource = (new SongResource($song = factory('App\Song') - > create())) - > jsonSerialize();
   }
}
[...]
$this - > assertArraySubset([
   'title' => $song - > title,
   'rating' => $song - > rating
], $resource);
[...]
public
function testSongHasAlbumRelationship() {
   $resource = (new SongResource($song = factory('App\Song') - > create(["album_id" => factory('App\Album') - > create(['id' => 1])]))) - > jsonSerialize();
}
[...]
$this - > assertInstanceOf(AlbumResource::class, $resource["album"]);

Note: During verification it was determined that we could run these tests with the following command:

vendor / bin / phpunit
load more v
40%

In order to keep sending the query from the browser (which is recommended for optimal latency) but only target secured records, you can generate secured API keys from your backend and use them in your frontend code. The backend will then automatically enforce the security filters contained in the key; the user will not be able to alter them.,All our backend API clients contain helpers to generate secured API keys. It is highly recommended that you use them instead of generating secured API keys manually.,The result of the hashing is concatenated to the URL-encoded query parameters, and this content is encoded in Base64 to generate the final secured API key.,The data returned will usually be a few seconds behind real time, because userID usage may take up to a few seconds propagate to the different clusters.

1
2
3 {
   "message": "Invalid Application-Id or API-Key"
}
1
2
3
{
   "message": "Invalid Application-Id or API-Key"
}
load more v
22%

Send Time Optimization, Send Time Optimization Send emails at the best time to engage with your audience. ,An example of how to toggle tracking on a per-message basis. Note the o:tracking option. This will disable link rewriting for this message:,By default the message will be returned in JSON form with parsed parts. Links to the attachments will be included.

v3/<domain>/messages
load more v

Other "undefined-undefined" queries related to "Laravel REST API Type Preservation (Ex. how to prevent ints being sent as strings)"