{"id":19450847,"url":"https://github.com/morning-train/laravelresources","last_synced_at":"2026-04-11T16:37:04.739Z","repository":{"id":57019857,"uuid":"176886282","full_name":"Morning-Train/LaravelResources","owner":"Morning-Train","description":null,"archived":false,"fork":false,"pushed_at":"2023-05-08T05:13:54.000Z","size":249,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-08T06:09:05.274Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Morning-Train.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-21T06:48:09.000Z","updated_at":"2021-10-18T09:01:14.000Z","dependencies_parsed_at":"2022-08-22T20:31:13.481Z","dependency_job_id":null,"html_url":"https://github.com/Morning-Train/LaravelResources","commit_stats":null,"previous_names":[],"tags_count":123,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morning-Train%2FLaravelResources","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morning-Train%2FLaravelResources/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morning-Train%2FLaravelResources/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Morning-Train%2FLaravelResources/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Morning-Train","download_url":"https://codeload.github.com/Morning-Train/LaravelResources/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240637766,"owners_count":19833189,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T16:39:15.522Z","updated_at":"2026-04-11T16:37:04.702Z","avatar_url":"https://github.com/Morning-Train.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Resources and Operations for Laravel\nOperations are a set of actions on the server that handles all logic from setting up routes to handling client requests.\n\nA simple operation could be an index operation for getting all users and returning them to the client. \n\nAnother use-case could be triggering an action on an Eloquent model. \nWhen doing this, one would always have to validate user input, fetch the model, \nexecute the method and then perhaps return something meaningful to the user.\n\nThis is similar to a simple operation where we need to return a single instance of a model to the user \n(a traditional read call) - but it contains some additional logic to handle method execution.\n\nA delete operation is similar, in that it first fetches the model and the trigger the delete method on it.\n\n## Install\n\nVia Composer\n\n``` bash\n$ composer require morningtrain/laravel-resources\n$ php artisan vendor:publish --provider=\"MorningTrain\\Laravel\\Resources\\LaravelResourcesServiceProvider\"\n```\n\n## Configuration\nRegister your resources and operations in `config/resources.php`:\n\nThe first level key corresponds to namespace, and value is an array of your resource classes.\nYou can give resources custom names with array keys, and nest arrays of resources. Just make sure all items have unique keys.\n\nIf no key is provided for a resource, the `Str::snake(class_basename($resource))` is used.\nThis means if you have a class called `User` and another resource with the key `\"user\"` in the same namespace, they will collide.\n\nExample:\n``` php\n'api' =\u003e [\n    \\App\\Operations\\Api\\User::class,\n    'custom_user' =\u003e \\App\\Operations\\Api\\User::class,\n    \n    'nested' =\u003e [\n        \\App\\Operations\\Api\\User::class,\n        'custom_user' =\u003e \\App\\Operations\\Api\\User::class,\n        \n        'deep_nested' =\u003e [\n            \\App\\Operations\\Api\\User::class,\n            'custom_user' =\u003e \\App\\Operations\\Api\\User::class,\n        ],\n    \n    /*\n     * These two will not work together - non-unique resource name:\n     *  \\App\\Operations\\Api\\MyResource::class,\n     *  'my_resource' =\u003e \\App\\Operations\\Api\\User::class,\n     */\n],\n```\n\n\n## Examples\n\n\n### Index Operation\nThe purpose of the `Index` operation is to retrieve a list of model entries and return the list as JSON.\n\nThis code example is taken from the package and will illustrate how the Index operation is implemented.\n\n```php\n\u003c?php\n\nnamespace MorningTrain\\Laravel\\Resources\\Operations\\Eloquent;\n\nuse MorningTrain\\Laravel\\Resources\\Support\\Contracts\\EloquentOperation;\nuse MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Eloquent\\QueryToCollection;\nuse MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Eloquent\\QueryModel;\nuse MorningTrain\\Laravel\\Resources\\Support\\Pipes\\TransformToView;\n\nclass Index extends EloquentOperation\n{\n\n    const ROUTE_METHOD = 'get';\n\n    protected function beforePipes()\n    {\n        return [\n\n            /**\n             * Takes filters and model name as parameters and returns a new query object\n             */\n            QueryModel::create()-\u003emodel($this-\u003emodel)-\u003efilters($this-\u003egetCachedFilters()),\n\n            /**\n             * Trigger `get` on the query and returns the resulting collection\n             */\n            QueryToCollection::create(),\n\n            /**\n             * Transform the collection by applying any appends to each model in the entry\n             */\n            TransformToView::create()-\u003eappends($this-\u003eappends, $this-\u003eoverwrite_appends),\n        ];\n    }\n\n}\n\n```\n\nOn a project level, the Index class can be either implemented in a Resource or as a single Operation.\n\nThe following example is an `EloquentResource` implementing a single `Index` operation.\nThe operation is already added in the base class, so it is not really neccesary to have the static `$operations` property.\n\nIn our example, users are returned with a pagination filter applied.\n\n```php\n\u003c?php\n\nnamespace App\\Http\\Operations\\Api;\n\nuse MorningTrain\\Laravel\\Resources\\Support\\Contracts\\EloquentResource;\nuse MorningTrain\\Laravel\\Resources\\Operations\\Eloquent\\Index;\nuse MorningTrain\\Laravel\\Filters\\Filter;\nuse App\\Models\\User as Model;\n\nclass Users extends EloquentResource\n{\n\n    protected $model = Model::class;\n    \n    public static $operations = [\n        Index::class,\n    ];\n\n    public function getFilters(){\n        return [\n            Filter::paginate(),\n        ];\n    }\n\n}\n\n```\n\nThe following example is similar to the one above, but will have the operation as a single class.\n\n```php\n\u003c?php\n\nnamespace App\\Http\\Operations\\Api\\Users;\n\nuse MorningTrain\\Laravel\\Resources\\Operations\\Eloquent\\Index as Operation;\nuse MorningTrain\\Laravel\\Filters\\Filter;\nuse App\\Models\\User as Model;\n\nclass IndexUsers extends Operation\n{\n\n    protected $model = Model::class;\n\n    public function getFilters(){\n        return [\n            Filter::paginate(),\n        ];\n    }\n\n}\n\n```\n\n\n## Design principles\nMuch of what we do with operations can be achieved using a traditional MVC approach. \nIt is however easy for controller logic to degrade into duplicated and badly structured code.\n\nOur goal when implementing *operations* in our setup is to gain a way to greatly increase code reuse.\nWhen we have found a good solution for solving a specific task, why not reuse it for all similar tasks?\n\n### Resources\nWhat we call a *resource* in this context, is essentially a collection or group of operations. \nIt is inside of a resource, that each and every operation is initialised and configured. \n\n### Configuration\nHaving operations being configurable means that we can build a multipurpose operation that is used in similar situations but with slightly different behaviour.\n\nA common example could be that of a typical index operation to query an Eloquent model based on a set of filters and return a collection to the user.\n\nIn this case, our Index operation is configured to use a specific model class (as would be the case with all Eloquent operations) and an array of filters.\n\nSo far we have only been mentioning API operations, but an operation could also be a normal page request. \nIn this case it would be configured to have a prettier route path and for instance to return a given view.\nFor most of our use cases, the page operation renders a view that outputs a desired React component.\n\nIt is a given that all operations should be configured to be protected by a permission layer in order to control access. \nUsing Laravel, we are in the end utilizing the underlying policy and gate system to control access. \nIt is spiced up with an extra permission laravel package to create the needed models and database structures to support roles and permissions.\n\n### Micro tasks using pipelines\nWith the release of version 2.x of our [Laravel package](https://packagist.org/packages/morningtrain/laravel-resources), \nthe main logic of operations are split into multiple smaller tasks in order to allow for futher code reuse. \nThis is done using the Laravel pipeline setup that are also used internally by Laravel for handling middlewares. \n\nDocumentation for Pipelines in Laravel 5.7 can be found [here](https://laravel.com/api/5.7/Illuminate/Pipeline/Pipeline.html). \n\nThis allows for a higher degree of customization between similar operations while keeping base logic decoupled.\n\nEvery minor task is called a *Pipe* to reflect it being a part of an operation pipeline.\n\nExamples of pipes:\n\n - Validate: Validates the incoming http request using Laravel compliant validation rules\n - Filter query: It allows for a DB query builder instance to be filtered based on HTTP request variables.\n - Prepare response payload: The return value of the main operation logic is uniformly transformed into a payload JSON object. \n \nHaving operations structured as a pipeline also keeps actual code/logic out of the operations themselves. What is left is more or less a configurable class.\nTo make changes and adaptations to how an operation works, one would only have to copy/extend the operations and changes to the pipeline.\nIn most cases, no addition code is required.  \n\n## Pipes\n\n### TriggerOnModel\nThe *TriggerOnModel* pipe expects the data returned by the previous pipe to be a Eloquent Model.\nIt will trigger a method on the model or the provided closure with the current model instance.\n\nIt can be used like this:\n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Eloquent\\TriggerOnModel::create()\n    -\u003etrigger('someModelMethod')\n```\n\nOr like this using a closure: \n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Eloquent\\TriggerOnModel::create()\n    -\u003etrigger(\n        fn(Model $model) =\u003e $model-\u003esomeModelMethod()\n    )\n```\n\nThe *Action* eloquent operation is basically a *Read* operation with a TriggerOnModel pipe.\n\n### TriggerOnModelsInCollection\nThe *TriggerOnModelsInCollection* pipe is similar to *TriggerOnModel*. \nInstead of expecting a model, it expects a collection of models and will execute the trigger for exery model instance in the collection.\n\n### QueryToCollection\nTriggers *get* on the query returned by the previous pipe and returns the result.\n\n### QueryToModel\nTriggers *first* on the query returned by the previous pipe and returns the result.\n\n### QueryToCount\nTriggers *count* on the query returned by the previous pipe and returns the result.\n\n\n\n### ToResourceView\nIt expects the previous pipe to return a Model or Collection instance.\nThe data (Model or Collection) will be passed into a [Laravel Resource](https://laravel.com/docs/8.x/eloquent-resources)\nand the resulting array will be returned.\n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\ToResourceView::create()\n    -\u003eview(MyResourceView::class)\n```\n\nMost Eloquent operations are already configured to use this pipe, so it will be possible to configure the resource like this:\n\n```php\npublic function readOperation(Read $read)\n{\n    $read-\u003emodel(MyModel::class)\n         -\u003eresourceView(MyModelResource::class);\n}\n```\n\n### SetEnv\nThis pipe sets context environment variables. \nIt is configurable using the *environment* method.\n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Context\\SetEnv::create()\n    -\u003eenvironment(['env' =\u003e 'variable'])\n```\n\nThe *environment* method is a proxy of `Context::env`, \nwhich means that it will also be able to take a closure that is executed when the ENV is generated.\n\n### BladeView\nThe *BladeView* pipe returns a blade view matching the configuration.\n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Pages\\BladeView::create()\n    -\u003epath('path.to.blade.view')\n    -\u003eparameters(['blade' =\u003e 'parameters'])\n```\n\n\n### RespondWithPageEnv\nThe *RespondWithPageEnv* pipe return the current ENV from Context as the response\nif it is an AJAX request. It can be used to get the ENV of a page by calling its route using Ajax.\n\n```php\n\\MorningTrain\\Laravel\\Resources\\Support\\Pipes\\Pages\\RespondWithPageEnv::create()\n```\n\n\n\n## Credits\nThis package is developed and actively maintained by [Morningtrain](https://morningtrain.dk).\n\n\u003c!-- language: lang-none --\u003e\n     _- _ -__ - -- _ _ - --- __ ----- _ --_  \n    (         Morningtrain, Denmark         )\n     `---__- --__ _ --- _ -- ___ - - _ --_ ´ \n         o                                   \n        .  ____                              \n      _||__|  |  ______   ______   ______ \n     (        | |      | |      | |      |\n     /-()---() ~ ()--() ~ ()--() ~ ()--() \n    --------------------------------------\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorning-train%2Flaravelresources","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorning-train%2Flaravelresources","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorning-train%2Flaravelresources/lists"}