Ability to use Model from third-party Laravel app via API with eloquent support.
Transfers all model actions to API calls.
Goes hand in hand with Laravel API model server package.
// config/database.php
'api_connection' => [
'driver' => 'laravel_api_model',
'database' => 'YOUR_ENDPOINT_URL', // https://example.com/api/model
// If your API doesn't require any authentication you can remove 'auth' from your config
'auth' => [
'type' => 'oauth', // package supports 'oauth' and 'passport_client_credentials'
'url' => 'YOUR_AUTH_URL',
'client_id' => '',
'client_secret' => '',
],
// Headers you want to append to every request (except auth requests)
'headers' => [
// ...
],
'array_value_separator' => ',',
'max_url_length' => 2048, // default: 2048
],
use FriendsOfCat\LaravelApiModel\Models\ApiModel
class User extends ApiModel
{
protected $connection = 'api_connection';
}
You can use this model as a regular one. Important thing is to extend ApiModel
and set api connection.
- cross database relations (with usage of
whereHas(...)
, eager-loads are supported) - JOIN
- upsert
- paginator - wip
You can use complex queries (although nested logic is not currently supported by laravel-api-model-server
) while retrieving, updating or deleting data.
User::isActive()
->where(function($q){
$q->where('id', '>', 1)
->orWhere(function ($q) {
$q->whereId(0)
->orWhereIn('created_at', [3,4,5]);
})
->whereIn('updated_at', [1,2,3,4,5]);
})
->orWhere(function($q) {
$q->where('id', 3)->where(function($q){
$q->whereDeletedAt(2)
->orWhereNull('type');
})
->where('updated_at', 'like', '%2012%');
})
->where('type', 1)
->get()
Transforms to request (GET):
https:://example.com/api/model/users?filter[0:and:active:e]=1&filter[0:and:id:gt]=1&filter[1:and:id:e]=0&filter[1:or:created_at:in]=3%2C4%2C5&nested=and%2C0%3Aor%2C0%3Aor%2C2%3Aand&filter[0:and:updated_at:in]=1%2C2%2C3%2C4%2C5&filter[2:and:id:e]=3&filter[3:and:deleted_at:e]=2&filter[3:or:type:is_null]=1&filter[0:and:updated_at:like]=%252012%25&filter[0:and:type:e]=1
Example response data:
[
{
"id": 1,
"type": 1,
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
},
{
"id": 2,
"type": 1,
"created_at": "2012-10-13T18:55:16",
"updated_at": "2012-10-13T18:55:16"
}
]
Result is eloquent model collection, same as using traditional, non API, approach.
User::where('id', '=', 1)->first();
// OR
User::find(1);
Transforms to request (GET):
https:://example.com/api/model/users?filter[and:id:e]=1&limit=1
Example response data:
[
{
"id": 1,
"name": "Mark",
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
}
]
Result is eloquent model, same as using traditional, non API, approach.
User::count();
User::avg('age');
User::min('age');
User::max('age');
// you can use complex queries with any aggregate function
User::where('created_at', '>', now()->subDays(7))->sum('credit');
Transforms to request (GET):
https:://example.com/api/model/users?filter[and:created_at:gt]=2012-10-13T17:55:16&queryType=sum,credit
Example response data:
[
{
"aggregate": 1000
}
]
Eloquent expects the result to be in the format showed in an example response.
Return value is the value of aggregate
key from the response.
// Example:
$totalCredit = User::where('created_at', '>', now()->subDays(7))->sum('credit');
$totalCredit === 1000 // true
User::where('created_at', '>', now()->subDays(7))->exists();
Transforms to request (GET):
https:://example.com/api/model/users?filter[and:created_at:gt]=2012-10-13T17:55:16&queryType=exists
Example response data:
[
{
"exists": true
}
]
Eloquent expects the result to be in the format showed in an example response.
Return value is the value of exists
key from the response.
// Example:
$totalCredit = User::where('created_at', '>', now()->subDays(7))->exists();
$totalCredit === true // true
You can use local or global scopes like you normally do.
Custom functionality especially useful when server does not wish to expose its underlying logic.
If the server is using laravel-api-model-server
package, and has allowed API to use requested scope,
it will be executed on server side.
User::externalScope('isActive')->first()
Transforms to request (GET):
https:://example.com/api/model/users?filter[and:isActive:scope]=&limit=1
User::externalScope('isFrom', ['New York', 'Boston'])->first()
Transforms to request (GET):
https:://example.com/api/model/users?filter[and:isFrom:scope]=New+York%2CBoston&limit=1
Example response data:
[
{
"id": 1,
"name": "Mark",
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
}
]
User::with([
'details', // relation defined in our local model
'undefined', // relation NOT defined in our local model
'external', // relation defined in our local model but related to model with same API connection
])->get()
/*
Every relation passed down to with() is inspected prior to query execution.
a) If relation is not set on our model, we assume it is external and we pass it with the query to server
and it is excluded from default eager loading.
b) If relation is defined AND has same database connection as our API model,
we pass it with the query to server and it is excluded from default eager loading.
From our above example, 'undefined' and 'external' are passed down to new externalWith() method.
Original with() is modified to hold only 'details' to ensure correct default eager loading.
*/
Transforms to request (GET):
https:://example.com/api/model/users?include=undefined,external
Example response data:
[
{
"id": 1,
"type": 1,
"undefined": {
"id": 1,
"title": "test"
},
"external": [
{
"id": 1,
"title": "test"
},
{
"id": 2,
"title": "test"
}
],
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
},
// ...
]
After receiving response, default eager loading take place, so query to receive details
is executed.
As undefined
was not defined on our end, but we receive data with key 'undefined' in response, it will be mixed with other attributes and can cause problems while saving (if UPDATE / CREATE
is allowed in API).
On the other hand, external
is defined on our end, and it is relation to another API model. This will be hydrated properly alongside other relations as proper eloquent collection in related
bucket.
User::insert([
['name' => 'Mark'],
['name' => 'Jason'],
])
Transforms to request (POST):
// https:://example.com/api/model/users
// --------------------------------------
// Data:
{
"getId": false,
"values": [
{
"name": "Mark",
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
},
{
"name": "Jason",
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
}
]
}
Example response data:
{
"bool": true
}
Possible values of bool
:
true
=> models were successfully created
false
=> models were not created
This Boolean
value is expected by model instance and is returned as a result of insert operation.
// Example:
$users = User::insert([
['name' => 'Mark'],
['name' => 'Jason'],
]);
$users === true // true
$user = new User();
$user->name = 'Mark';
$user->save();
// OR
$user = User::create(['name' => 'Mark']);
Transforms to request (POST):
// https:://example.com/api/model/users
// --------------------------------------
// Data:
{
"getId": true,
"values": [
{
"name": "Mark",
"created_at": "2012-10-13T17:55:16",
"updated_at": "2012-10-13T17:55:16"
}
]
}
Example response data:
{
"id": 1
}
Possible values of id
:
mixed
=> primary key value of newly created instance (real column doesn't need to be called 'id')
false
=> model was not created
The value of id
is hydrated to your existing model instance as a value of a primary key.
// Example:
$user = new User();
$user->name = 'Mark';
$user->save();
// OR
$user = User::create(['name' => 'Mark']);
$user->id === 1 // true
User::where('created_at', '>', now()->subDays(7))->update(['name' => 'Mark']);
Transforms to request (PUT):
// https:://example.com/api/model/users
// --------------------------------------
// Data:
{
"params": {
"filter": {
"and:created_at:gt": "2012-10-13T17:55:16"
}
},
"data": {
"name": "Mark"
}
}
Example response data:
42
It returns an integer that represents the number of updated entries.
// Example:
$numberOfUpdatedUsers = User::where('created_at', '>', now()->subDays(7))->update(['name' => 'Mark']);
$numberOfUpdatedUsers === 42 // true
// Example:
$user = User::find(1);
$user->name = 'Mark';
$user->save();
Transforms to request (PUT):
// https:://example.com/api/model/users
// --------------------------------------
// Data:
{
"params": {
"filter": {
"and:id:e": 1
}
},
"data": {
"name": "Mark"
}
}
$numberOfDeletedUsers = User::where('created_at', '>', now()->subDays(7))->delete();
Transforms to request (DELETE):
// https:://example.com/api/model/users
// --------------------------------------
// Data:
{
"filter": {
"and:created_at:gt": "2012-10-13T17:55:16"
}
}
Example response data:
42
It returns an integer that represents the number of deleted entries.
// Example:
$numberOfDeletedUsers = User::where('created_at', '>', now()->subDays(7))->delete();
$numberOfDeletedUsers === 42 // true