phpsa / laravel-api-controller
A laravel api base controller with basic CRUD mapped to the model
Fund package maintenance!
phpsa
Installs: 30 217
Dependents: 5
Suggesters: 0
Security: 0
Stars: 47
Watchers: 6
Forks: 17
Open Issues: 3
Requires
- php: ^8.1
- laravel/framework: ^9.0|^10.0|^11.0
Requires (Dev)
- doctrine/dbal: >=2.3
- mockery/mockery: ^1.0
- nunomaduro/larastan: ^2.0
- orchestra/testbench: ^7.0|^8.0|^9.0
- phpunit/phpunit: ^9.5|^10.0
- dev-master
- v7.3.2
- v7.3.1
- v7.3.0
- v7.2.0
- v7.1.6
- v7.1.5
- v7.1.4
- v7.1.3
- v7.1.2
- v7.1.1
- v7.1.0
- v7.1.0-beta.1
- v7.0.2
- v7.0.1
- v7.0.0
- 6.x-dev
- v6.1.1
- V6.1.0
- V6.0.0
- 5.x-dev
- v5.9.6
- v5.9.5
- v5.9.4
- v5.9.3
- v5.9.2
- v5.9.1
- v5.9.0
- v5.8.4
- v5.8.3
- 5.8.3-b.2
- 5.8.3-b.1
- v5.8.2
- v5.8.1
- v5.8.0
- v5.7.0
- v5.6.1
- v5.6.0
- v5.5.1
- v5.5.0
- v5.4.1
- v5.4.0
- v5.3.1
- v5.3.0
- v5.2.0
- v5.1.0
- v5.0.0
- 5.0.0-alpha.2
- 5.0.0-alpha.1
- v4.3.0
- v4.2.3
- v4.2.2
- v4.2.1
- v4.2.0
- v4.1.1
- v4.1.0
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- v4.0.0-beta.2
- v4.0.0-beta.1
- 3.x-dev
- 3.3.1
- v3.3.0
- 3.2.3
- 3.2.2
- 3.2.1
- 3.2.0
- 3.2.0-beta.4
- 3.2.0-beta.3
- 3.2.0-beta.2
- 3.2.0-beta.1
- v3.1.1
- v3.1.0
- v3.0.0
- 3.0.0-a.1
- 2.x-dev
- 2.5.0
- 2.4.0
- v2.3.0
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.1
- 2.0.0
- 2.0.0-rc.1
- 2.0.0-b.3
- 2.0.0-b.2
- 2.0.0-b.1
- 2.0.0-a.1
- 1.x-dev
- 1.25.0-b.1
- 1.24.0
- 1.23.0
- 1.22.1
- 1.22.0
- 1.21.1
- 1.21.0
- 1.20.3
- 1.20.2
- 1.20.1
- 1.20.0
- 1.19.0
- 1.18.0
- 1.17.2
- 1.17.1
- 1.17.0
- 1.16.0
- 1.15.0
- 1.14.0
- 1.13.0
- 1.12.1
- 1.12.0
- 1.12.0-b2
- 1.12.0-b1
- 1.11.0
- 1.11.0-b.3
- 1.11.0-b.2
- 1.10.0
- 1.10.0-b1
- 1.9.1
- 1.9.0
- 1.9.0-b.2
- 1.9.0-b.1
- 1.8.0
- 1.7.2
- 1.7.1
- 1.7.0
- 1.6.0
- 1.6.0-b.1
- 1.5.0
- 1.5.0-rc.12
- 1.5.0-rc.11
- 1.5.0-rc.10
- 1.5.0-rc.9
- 1.5.0-rc.8
- 1.5.0-rc.7
- 1.5.0-rc.6
- 1.5.0-rc.5
- 1.5.0-rc.4
- 1.5.0-rc.3
- 1.5.0-rc.2
- 1.5.0-rc.1
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.2-b.2
- 1.4.2-b.1
- 1.4.1
- 1.4.0
- 1.3.1
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.1-rc.2
- 1.1.1-rc.1
- 1.1.0
- 1.1.0-rc.3
- 1.1.0-rc.2
- 1.1.0-rc.1
- 1.0.0
- 1.0.0-rc.2
- 1.0.0-rc.1
- 1.0.0-b.2
- 1.0.0-b.1
- 0.5.0-b.1
- 0.4.0
- 0.4.0-a.1
- 0.3.1
- 0.3.0
- 0.3.0-rc.1
- 0.2.1
- 0.2.0
- 0.2.0-rc.1
- 0.2.0-b.2
- 0.2.0-b.1
- 0.2.0-a.2
- 0.2.0-a.1
- 0.1.3
- 0.1.2
- 0.1.1
- 0.1.0
- dev-ricks_fixes
- dev-feat_request_validated_only
- dev-improve-relationships
- dev-release/5.x
- dev-fix/remove-tostring-calls-on-stringable
- dev-feat-query-parser-moved-to-trait
- dev-fix-non-relations
- dev-relationship_remap_documentation
- dev-add-restore-action-for-soft-deletes
- dev-relation_rempa_bakcup
- dev-josh-master-patch-92633
- dev-josh-master-patch-69646
- dev-105-user-model-and-namespace-in-policy-stub-is-hardcoded-for-query-functions
- dev-fix-inherits-bad-practice
- dev-104-minor-whitespace-issues-in-the-stub-files
- dev-fix_non-standard+route-binding
- dev-107-wrong-database-used-when-using-tenancy-for-laravel
- dev-98-handling-of-null-values-when-saving-related-data
- dev-103-make-api-model-causes-the-pivot-option-does-not-exist-message
- dev-feature/pass_resource_to_gates
- dev-101-differing-policy-paths
- dev-102-path-for-nested-controller-stub-is-incorrect
- dev-fix-child-models-always-returning-like-default
- dev-fix-nested-controller-stub-path
- dev-docs-update-readme
- dev-fix/map-related-on-array
- dev-beta
- dev-feature/gated_fields
- dev-feature/improved_cli_tools
- dev-analysis-AD4MMZ
- dev-parse-sync-fields-case
- dev-feature/pivot-values
- dev-feature/response_overiders
This package is auto-updated.
Last update: 2024-10-08 21:13:12 UTC
README
[[TOC]]
Basic CRUD API Methods that can be extended for your models by default has a list, show, update, add and delete endpoint to interact with your model.
Installation
Install via composer
composer require phpsa/laravel-api-controller
Publish Configuration File (optional - if you need to change any of the default configurations)
php artisan vendor:publish --provider="Phpsa\LaravelApiController\ServiceProvider" --tag="config"
Usage
CLI Commands
artisan make:api:controller {ControllerName}
to generate the controllerartisan make:api:policy {PolicyName} -m {Model}
to generate a policy fileartisan make:api:resource {ResourceName|CollectionName}
to geneate the response resource
This will create a Api/ModelNameController for you and you will have the basic routes in place as follows:
- GET
api/v1/{model_name}
- list all/paged/filtered (class::index) - GET
api/v1/{model_name}/$id
- Show a specified id (class::show) - POST
api/v1/{model_name}
- Insert a new record (class::store) - PUT
api/v1/{model_name}/$id
- Replace an existing record (class::update) - PATCH
api/v1/{model_name}/$id
- Update an existing record (class::update) - DELETE
api/v1/{model_name}/$id
- Delete an existing record (class::destroy)
If you specify --soft-deletes
option on make:api:controller
it will also create an additional restore
controller endpoint & route:
- PATCH
api/v1/{model_name}/$id
- Restore a soft-deleted record (class::restore). This only works for models with Soft Deletes enabled.
You can override the methods by simply putting in your own methods to override - method names in braces above
Events
- POST (class::store) - triggers a new
Phpsa\LaravelApiController\Events\Created
Event which has the new record available as$record
- PUT (class::update) - triggers a new
Phpsa\LaravelApiController\Events\Updated
Event which has the updated record available as$record
- DELETE (class::destroy) - triggers a new
Phpsa\LaravelApiController\Events\Deleted
Event which has the deleted record available as$record
Policies
Policies: https://laravel.com/docs/6.x/authorization#generating-policies
Generate with php artisan make:policy PostPolicy --model=Post
- Get list - calls the
viewAny
policy - Get single - calls the
view
policy - Post New - calls the
create
policy - Put Update - calls the
update
policy - Delete item - calls the
delete
policy
Query/Data modifiers in policies for the api endpoints
qualifyCollectionQueryWithUser($user, $repository)
-> return void - add any queries to the repository (ie ->where('x','))qualifyItemQueryWithUser($user, $repository)
-> return void - add any queries to the repository (ie ->where('x','))qualifyStoreDataWithUser($data)
- return the updated data arrayqualifyUpdateDataWithUser($data)
- return the updated data array
Resources / Collections (Transforming)
Resources: https://laravel.com/docs/6.x/eloquent-resources
Generate with
php artisan make:apiresource UserResource
and php artisan make:api:resource UserCollection
Change the Resource to extend from:
use Phpsa\LaravelApiController\Http\Resources\ApiResource
for your resource
use Phpsa\LaravelApiController\Http\Resources\ApiCollection
for your resource collection
in your controller override the following params:
protected $resourceSingle = UserResource::class; protected $resourceCollection = UserCollection::class;
Snake vs Camel
- middleware to convert all camel to snake:
Phpsa\LaravelApiController\Http\Middleware\SnakeCaseInputs
- set request header
X-Accept-Case-Type
to eithersnake
orcamel
to alter your data response
Filtering
stable option that will be removed once experimental stable
For the get command you can filter by using the following url patterns
In / Not In
You can pass to the filters an array of values
ie: filter[user_id]=1||2||||4||7
or filter[user_id!]=55||33
Null / Not Null (introduced 1.23.0)
If you need to filter on whether a field is null or not null, you can use the filter param as of version 1.23.0 EG: filter[age]=NULL
or filter[age!]=NULL
. Note that NULL must be uppercase.
Older versions Add a scope to your model: eg
public function scopeAgeNull(Builder $builder, $isNull = true){ $isNull ? $builder->whereNull('age') : $builder->whereNotNull('age'); }
Add to your allowedScopes and can then be called in url as ?ageNull=1
for where null and ?ageNull=0
for where age not null
Filtering
- use the url pattern
filters[column][operator]=value
egfilters[age][>]=18&filters[title][contains]=testing
-
Null =
filters[age]=NULL
will generatewhere age is null
-
Json columns =
filters[meta->seo][is]=enabled
will generate
where json_unquote(json_extract(`meta\`, '$."seo"')) = 'enabled'
- Relations:
filters[relationName][has]
orfilters[relationName][!has]
orfilters[relation_name][not_has]
- Relations filtering
filters[tags][has][slug]=my_slug
- Relations
filters[tags]=true
orfilters['tags.slug']=myslug
filters[tags.slug][!]=myslug
filters[tags.slug][!][contains]=money
Enforced scopes / query filters on a controller override the
protected function getNewQuery(): Builder
{
return resolve($this->model())->newQuery();
}
method in your controller to include any additional queries / scopes etc.
Requests
We have added a request macro to enable you to set these on your request as needed:
eg:
public function index(Request $request) { $request->apiFilter('user_id', auth()->id()); $request->apiFilter('owner_id', 'not_equals', auth()->id()); $request->apiFilter('age', '>=', 5); $request->apiFilter('age', '<=', 10); $request->apiInclude(['owner','user']); $request->apiAddFields(['x','y','z']);
- filter method is
apiFilter($column, $value)
orapiFilter($column, $operator, $value)
- addfields method is
apiAddFields(array|string $fieldsOrAttributesToInclude)
- include method is
apiInclude(array|string $relations)
In your controller, we generally use request->all()
for the filling of models. Should you wish to use a more strict option, you can opt into using validated values only by calling $this->setOnlyValidated()
in your controller which will then use the request->validated()
to get the data (Note: this means it will not take any merged information from postValidation).
Scopes
In addition to filtering, you can use Laravel's Eloquent Query Scopes to do more complex searches or filters.
Simply add an $allowedScopes
to your ApiResource
, and that scope will be exposed as a query parameter.
Assuming you have a scopeFullname
defined on your Eloquent Model, you can expose this scope to your API as follows:
protected static $allowedScopes = [ 'fullname' ];
Given the above $allowedScopes
array, your API consumers will now be able to request ?fullname=John
. The query parameter value will be passed to your scope function in your Eloquent Model.
Filtering on related models
You can easily filter using any related model that is configured for include
. Simply specify ?filter[model.field]=123
in your query string. The same filter options above apply to related fields.
Grouped Filtering Scopes
filter_by_relation_group[a][name]=weight&filter_by_relation_group[a][value][>]=900&filter_by_relation_group[b][name]=color&filter_by_relation_group[b][value]=color
public function scopeFilterByRelationGroup(Builder $builder, array $wheres): void
{
$where = collect($wheres)->map(fn ($child) =>
$this->parseFiltersArray($child)
)->each(
fn($group, $key) => $builder->whereHas('Relation', function ($subQ) use ($group, $key) {
$group->each(
fn($filter, $column) => collect($filter)->each(fn($value, $comparison) => $this->buildQuery($column, $comparison, $value, $subQ))
);
}
)
);
}
Fields, Relationships, Sorting & Pagination
Fields
By default all fields are returned, you can limit that to specific fields in the following ways:
- Api Controller parameter
$defaultFields
default asprotected $defaultFields = ['*'];
- switch to include an array of fields - fields param in url querystring: ie
fields=id,name,age
= will only return those, this will also override the above. - in your response resource you can set the static::allowedFields to lock down which fields are returnable.
- This also controls which related resources are returnable. Include the key that is used in
$mapResources
(see "Relationships" below).
- This also controls which related resources are returnable. Include the key that is used in
addfields
andremovefields
params in url querystring will work with these.- Use laravel eloquent model
$appends
property to automatically include custom attribute accessors.
Gated Response Fields
Gates can be used to control access to fields and related resources, by defining $gatedFields
:
protected static array $fieldGates = [
'gate-one' => [
'fieldA',
'fieldB',
],
'gate-two' => [
'fieldA',
'fieldC,
'relatedResourceD'
]
];
Each specified gate will be used to determine whether that set of fields will be included.
Each gate will be passed the resource as well as the user, so it can test whether the user should be allowed to access that specific resource.
Example gate definition:
Gate::define(
'supervises-the-group',
fn ($user, Group $group) => (int) $user->id === $group->supervisor_id
);
Relationships
- Using the relationships defined in your models, you can pass a comma delimited list eg
include=join1,join2
which will return those joins (one or many).
Simply add a protected static $mapResources
to your Resource
to define which resources to assign your related data. E.e., for a one to many relationship, you should specify a collection, and a one-to-one relationship specify the related resource directly. This will allow the API to properly format the related record.
protected static $mapResources = [
'notes' => NotesCollection::class,
'owner' => OwnerResource::class
];
- You can automatically update and create related records for most types of relationships. Just include the related resource name in your POST or PUT request.
- Important: if you are using
$defaultFields
and/or$allowedFields
in your resource, the related resource key from$mapResources
must also be included in those lists for that related resource to be included.
For BelongsToMany
or MorphToMany
relationships, you can choose the sync strategy. By default, this will take an additive strategy. That is to say, related records sent will be ADDED to any existing related records. On a request-by-request basis, you can opt for a sync strategy which will remove the pivot for any related records not listed in the request. Note the actual related record will not be removed, just the pivot entry.
To opt for the sync behavaiour, set ?sync[field]=true
in your request.
Sorting
- Sorts can be passed as comma list aswell, ie
sort=age asc
orsort=age asc,name desc,eyes
- generates sql ofsort age asc
andsort age asc, name desc, eyes asc
respectively - Default sort can also be added on the controller using by overrideing the
protected $defaultSort = null;
parameter
Pagination
- pagination can be enabled/disbled on the controller by overriding the
protected $defaultLimit = 25;
on the controller - pagination can also be passed via the url using
limit=xx&page=y
- pagination can also be limited to a max per page by overriding the
protected $maximumLimit = false;
parameter
Validation
- When Posting a new record, validation can be done by adding a
rulesForCreate
method to your controller returning an array eg
[ 'email' => 'required|email', 'games' => 'required|numeric', ]
see https://laravel.com/docs/5.8/validation#conditionally-adding-rules
- for updating a record, add a method
rulesForUpdate
per above.
Defaults
The following parameters are set in the Base Api controller and can be overwritten by your Controller on a case by case basis:
protected $resourceSingle = JsonResource::class;
Collection to use for your single resourceprotected $resourceCollection = ResourceCollection::class;
Collection to use for your resource collectionprotected $defaultFields = ['*'];
Default Fields to respond withprotected $defaultSort = null;
Set the default sorting for queries.protected $defaultLimit = 25;
Number of items displayed at once if not specified. (0 = maximumLimit)protected $maximumLimit = 0;
Maximum limit that can be set via $_GET['limit']. - this ties in with the defaultLimit aswell, and if wanting to disable pagination , both should be 0. ) will allow all records to be returned in a single call.protected $unguard = false;
Do we need to unguard the model before create/update?
Scopes
SoftDeleted Records
add the Phpsa\LaravelApiController\Model\Scopes\WithSoftDeletes
trait to your model,
add to your resource file:
class MyModelResource extends ApiResource { protected static $allowedScopes = [ 'withTrashed', 'onlyTrashed' ];
you can now append withTrashed=1
or onlyTrashed=1
to your query.
Responses
you can override responses for each point by overriding the following protected methods:
- handleIndexResponse
- handleStoreResponse
- handleShowResponse
- handleUpdateResponse
- handleDestroyResponse
Perforance Tips
Cache Table column definitions
-- introduced https://github.com/phpsa/laravel-api-controller/pull/118/files
add the ability to cache the table definitions to reduce calls to fetch table columns, to enable either enable in the config file or set the PHPSA_API_CACHE_TABLE_COLUMNS
variable to true.
Raw Pagination gets
-- handleIndexAction will use full eloquent models, handleIndexActionRaw will bypass eloquent and use raw responses from the database.
Security
If you discover any security related issues, please email instead of using the issue tracker.