1"use strict";(self.webpackChunkphpvms_docs=self.webpackChunkphpvms_docs||[]).push([[5444],{89344(e,n,r){r.r(n),r.d(n,{assets:()=>i,contentTitle:()=>d,default:()=>p,frontMatter:()=>o,metadata:()=>s,toc:()=>a});const s=JSON.parse('{"id":"developers/addons/routing-controllers","title":"Routing & Controllers","description":"Routing in a phpvms module follows the standard","source":"@site/docs/developers/addons/routing-controllers.md","sourceDirName":"developers/addons","slug":"/developers/addons/routing-controllers","permalink":"/8.x/developers/addons/routing-controllers","draft":false,"unlisted":false,"editUrl":"https://github.com/phpvms/docs/tree/master/docs/developers/addons/routing-controllers.md","tags":[],"version":"current","frontMatter":{"id":"routing-controllers","title":"Routing & Controllers"},"sidebar":"docs","previous":{"title":"Publishing to the Addon Registry","permalink":"/8.x/developers/addons/publishing"},"next":{"title":"Database","permalink":"/8.x/developers/addons/database"}}');var l=r(74848),t=r(28453);const o={id:"routing-controllers",title:"Routing & Controllers"},d=void 0,i={},a=[{value:"Route files",id:"route-files",level:2},{value:"Web routes (routes/web.php)",id:"web-routes-routeswebphp",level:3},{value:"API routes (routes/api.php)",id:"api-routes-routesapiphp",level:3},{value:"Standard Laravel features",id:"standard-laravel-features",level:3},{value:"RouteServiceProvider",id:"routeserviceprovider",level:3},{value:"Controllers",id:"controllers",level:2},{value:"Generating controllers",id:"generating-controllers",level:3},{value:"Form requests",id:"form-requests",level:3},{value:"API resources",id:"api-resources",level:3},{value:"A full resource controller",id:"a-full-resource-controller",level:3},{value:"Middleware",id:"middleware",level:2},{value:"Custom middleware",id:"custom-middleware",level:3},{value:"Getting the current user",id:"getting-the-current-user",level:2}];function c(e){const n={a:"a",code:"code",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,t.R)(),...e.components};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsxs)(n.p,{children:["Routing in a phpvms module follows the standard\n",(0,l.jsx)(n.a,{href:"https://laravel.com/docs/routing",children:"Laravel routing"})," format. Each generated\nmodule has its own ",(0,l.jsx)(n.code,{children:"routes/"})," directory with separate files for web pages and API\nendpoints, registered automatically by the module's ",(0,l.jsx)(n.code,{children:"RouteServiceProvider"}),"."]}),"\n",(0,l.jsx)(n.h2,{id:"route-files",children:"Route files"}),"\n",(0,l.jsx)(n.p,{children:"A generated module ships with two route files:"}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"File"}),(0,l.jsx)(n.th,{children:"URL prefix"}),(0,l.jsx)(n.th,{children:"Middleware group"}),(0,l.jsx)(n.th,{children:"Default name prefix"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"routes/web.php"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"/{module}"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"web"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"{module}."})})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"routes/api.php"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"/api/{module}"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"api"})}),(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"api.{module}."})})]})]})]}),"\n",(0,l.jsx)(n.h3,{id:"web-routes-routeswebphp",children:"Web routes (routes/web.php)"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Modules\\Sample\\Http\\Controllers\\SampleController;\n\nRoute::middleware(['web'])\n ->prefix('sample')\n ->name('sample.')\n ->group(function () {\n Route::get('/', [SampleController::class, 'index'])->name('index');\n Route::get('/{post}
1', [SampleController::class, 'show'])->name('show');\n });\n"})}),"\n",(0,l.jsxs)(n.p,{children:["The named route ",(0,l.jsx)(n.code,{children:"sample.index"})," resolves to ",(0,l.jsx)(n.code,{children:"route('sample.index')"})," and the URL\n",(0,l.jsx)(n.code,{children:"/sample"}),"."]}),"\n",(0,l.jsx)(n.h3,{id:"api-routes-routesapiphp",children:"API routes (routes/api.php)"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"<?php\n\nuse Illuminate\\Support\\Facades\\Route;\nuse Modules\\Sample\\Http\\Controllers\\Api\\PostController;\n\nRoute::middleware(['api'])\n ->prefix('api/sample')\n ->name('api.sample.')\n ->group(function () {\n Route::apiResource('posts', PostController::class);\n });\n"})}),"\n",(0,l.jsxs)(n.p,{children:[(0,l.jsx)(n.code,{children:"apiResource"})," registers the standard ",(0,l.jsx)(n.code,{children:"index/show/store/update/destroy"})," routes \u2014\nsee the\n",(0,l.jsx)(n.a,{href:"https://laravel.com/docs/controllers#api-resource-routes",children:"Laravel API resource controllers docs"}),"."]}),"\n",(0,l.jsx)(n.h3,{id:"standard-laravel-features",children:"Standard Laravel features"}),"\n",(0,l.jsx)(n.p,{children:"All Laravel routing features work as you'd expect inside module route files:"}),"\n",(0,l.jsxs)(n.ul,{children:["\n",(0,l.jsxs)(n.li,{children:["Resource and API resource routes (",(0,l.jsx)(n.code,{children:"Route::resource"}),", ",(0,l.jsx)(n.code,{children:"Route::apiResource"}),")."]}),"\n",(0,l.jsxs)(n.li,{children:["Route model binding \u2014 ",(0,l.jsx)(n.code,{children:"Route::get('/posts/{post}', ...)"})," resolves a ",(0,l.jsx)(n.code,{children:"Post"})," by\nits primary key automatically."]}),"\n",(0,l.jsx)(n.li,{children:"Named routes and route groups."}),"\n",(0,l.jsxs)(n.li,{children:["Cross-module references \u2014 your module can call ",(0,l.jsx)(n.code,{children:"route('shop.checkout')"})," on\nroutes defined in another module."]}),"\n"]}),"\n",(0,l.jsx)(n.h3,{id:"routeserviceprovider",children:"RouteServiceProvider"}),"\n",(0,l.jsxs)(n.p,{children:["The provider at ",(0,l.jsx)(n.code,{children:"app/Providers/RouteServiceProvider.php"})," registers the route\nfiles and sets the controller namespace. You won't normally need to edit it. If\nyou delete it accidentally, regenerate with:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"php artisan module:route-provider Sample\n"})}),"\n",(0,l.jsx)(n.h2,{id:"controllers",children:"Controllers"}),"\n",(0,l.jsxs)(n.p,{children:["Controllers live under ",(0,l.jsx)(n.code,{children:"Modules/{Module}/app/Http/Controllers/"}),", with the\nnamespace ",(0,l.jsx)(n.code,{children:"Modules\\{Module}\\Http\\Controllers"}),". A minimal controller looks like:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"<?php\n\nnamespace Modules\\Sample\\Http\\Controllers;\n\nuse App\\Http\\Controllers\\Controller;\nuse Illuminate\\Http\\Request;\n\nclass SampleController extends Controller\n{\n public function index()\n {\n return view('sample::index');\n }\n}\n"})}),"\n",(0,l.jsxs)(n.p,{children:["The ",(0,l.jsx)(n.code,{children:"sample::index"})," view reference uses the module's ",(0,l.jsx)(n.code,{children:"alias"})," (from\n",(0,l.jsx)(n.code,{children:"module.json"}),") as a view-namespace prefix. See ",(0,l.jsx)(n.a,{href:"/8.x/developers/addons/templating",children:"Templating"}),"."]}),"\n",(0,l.jsx)(n.h3,{id:"generating-controllers",children:"Generating controllers"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"# Resource controller (default \u2014 generates index/create/store/show/edit/update/destroy)\nphp artisan module:make-controller PostController Sample\n\n# API-only controller (drops create/edit form methods)\nphp artisan module:make-controller PostController Sample --api\n\n# Plain controller (no methods)\nphp artisan module:make-controller PostController Sample --plain\n\n# Single-action invokable controller\nphp artisan module:make-controller PublishPostController Sample --invokable\n"})}),"\n",(0,l.jsx)(n.p,{children:"You can nest controllers in subdirectories. This is the convention for admin-\nand API-specific controllers:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"php artisan module:make-controller Admin/PostController Sample\n# \u2192 Modules/Sample/app/Http/Controllers/Admin/PostController.php\n# \u2192 namespace Modules\\Sample\\Http\\Controllers\\Admin\n\nphp artisan module:make-controller Api/PostController Sample --api\n# \u2192 Modules/Sample/app/Http/Controllers/Api/PostController.php\n# \u2192 namespace Modules\\Sample\\Http\\Controllers\\Api\n"})}),"\n",(0,l.jsxs)(n.p,{children:["Use route groups in ",(0,l.jsx)(n.code,{children:"web.php"})," or ",(0,l.jsx)(n.code,{children:"api.php"})," to wire these up:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"Route::middleware(['web', 'role:admin'])\n ->prefix('sample/admin')\n ->name('sample.admin.')\n ->group(function () {\n Route::resource('posts', \\Modules\\Sample\\Http\\Controllers\\Admin\\PostController::class);\n });\n"})}),"\n",(0,l.jsx)(n.h3,{id:"form-requests",children:"Form requests"}),"\n",(0,l.jsx)(n.p,{children:"Generate validation classes with:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"php artisan module:make-request StorePostRequest Sample\n# \u2192 Modules/Sample/app/Http/Requests/StorePostRequest.php\n"})}),"\n",(0,l.jsx)(n.p,{children:"Inside the request class:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"namespace Modules\\Sample\\Http\\Requests;\n\nuse Illuminate\\Foundation\\Http\\FormRequest;\n\nclass StorePostRequest extends FormRequest\n{\n public function authorize(): bool\n {\n return true;\n }\n\n public function rules(): array\n {\n return [\n 'title' => 'required|string|max:255',\n 'body' => 'required|string',\n ];\n }\n}\n"})}),"\n",(0,l.jsx)(n.p,{children:"Type-hint it in your controller method to get validated input automatically:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"public function store(StorePostRequest $request)\n{\n $post = Post::create($request->validated());\n return redirect()->
1route('sample.show', $post);\n}\n"})}),"\n",(0,l.jsx)(n.h3,{id:"api-resources",children:"API resources"}),"\n",(0,l.jsx)(n.p,{children:"For shaping JSON responses:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"php artisan module:make-resource PostResource Sample\nphp artisan module:make-resource PostCollection Sample --collection\n"})}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"return PostResource::collection(Post::all());\n"})}),"\n",(0,l.jsx)(n.h3,{id:"a-full-resource-controller",children:"A full resource controller"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"<?php\n\nnamespace Modules\\Sample\\Http\\Controllers;\n\nuse App\\Http\\Controllers\\Controller;\nuse Modules\\Sample\\Http\\Requests\\StorePostRequest;\nuse Modules\\Sample\\Http\\Requests\\UpdatePostRequest;\nuse Modules\\Sample\\Models\\Post;\n\nclass PostController extends Controller\n{\n public function index()\n {\n return view('sample::posts.index', [\n 'posts' => Post::latest()->paginate(15),\n ]);\n }\n\n public function create()\n {\n return view('sample::posts.create');\n }\n\n public function store(StorePostRequest $request)\n {\n $post = Post::create($request->validated());\n return redirect()->route('sample.posts.show', $post);\n }\n\n public function show(Post $post)\n {\n return view('sample::posts.show', compact('post'));\n }\n\n public function edit(Post $post)\n {\n return view('sample::posts.edit', compact('post'));\n }\n\n public function update(UpdatePostRequest $request, Post $post)\n {\n $post->update($request->validated());\n return redirect()->route('sample.posts.show', $post);\n }\n\n public function destroy(Post $post)\n {\n $post->delete();\n return redirect()->route('sample.posts.index');\n }\n}\n"})}),"\n",(0,l.jsx)(n.h2,{id:"middleware",children:"Middleware"}),"\n",(0,l.jsx)(n.p,{children:"Middleware lets you inject access checks into the request chain so you don't\nneed to repeat them in every method. phpvms provides three middleware aliases on\ntop of Laravel's defaults:"}),"\n",(0,l.jsxs)(n.table,{children:[(0,l.jsx)(n.thead,{children:(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.th,{children:"Middleware"}),(0,l.jsx)(n.th,{children:"Purpose"})]})}),(0,l.jsxs)(n.tbody,{children:[(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"auth"})}),(0,l.jsx)(n.td,{children:"Standard Laravel \u2014 request must come from a logged-in user."})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"role:user"})}),(0,l.jsx)(n.td,{children:"Logged-in phpvms user. Use for normal pilot pages."})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"role:admin"})}),(0,l.jsx)(n.td,{children:"Admin user. Use for admin-only screens."})]}),(0,l.jsxs)(n.tr,{children:[(0,l.jsx)(n.td,{children:(0,l.jsx)(n.code,{children:"api.auth"})}),(0,l.jsxs)(n.td,{children:["API key authentication. Use on ",(0,l.jsx)(n.code,{children:"routes/api.php"})," routes."]})]})]})]}),"\n",(0,l.jsx)(n.p,{children:"Apply middleware in route groups or directly on routes:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"Route::middleware(['web', 'role:user'])->prefix('sample')->group(function () {\n Route::get('/dashboard', [SampleController::class, 'dashboard']);\n});\n\nRoute::middleware(['api', 'api.auth'])->prefix('api/sample')->group(function () {\n Route::get('/stats', [Api\\StatsController::class, 'index']);\n});\n"})}),"\n",(0,l.jsx)(n.p,{children:"You can also apply middleware from inside a controller's constructor:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"public function __construct()\n{\n $this->middleware('auth');\n $this->middleware('role:admin')->only(['edit', 'update', 'destroy']);\n}\n"})}),"\n",(0,l.jsx)(n.h3,{id:"custom-middleware",children:"Custom middleware"}),"\n",(0,l.jsx)(n.p,{children:"Generate one with:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-shell",children:"php artisan module:make-middleware EnsurePostIsPublished Sample\n# \u2192 Modules/Sample/app/Http/Middleware/EnsurePostIsPublished.php\n"})}),"\n",(0,l.jsxs)(n.p,{children:["Register an alias in your module's ",(0,l.jsx)(n.code,{children:"RouteServiceProvider"})," if you want to use it\nas a string:"]}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"public function boot(): void\n{\n $this->app['router']->
1aliasMiddleware(\n 'post.published',\n \\Modules\\Sample\\Http\\Middleware\\EnsurePostIsPublished::class,\n );\n\n parent::boot();\n}\n"})}),"\n",(0,l.jsx)(n.p,{children:"Or apply it directly to a route by class name:"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"Route::get('/posts/{post}', [PostController::class, 'show'])\n ->middleware(\\Modules\\Sample\\Http\\Middleware\\EnsurePostIsPublished::class);\n"})}),"\n",(0,l.jsxs)(n.p,{children:["See the ",(0,l.jsx)(n.a,{href:"https://laravel.com/docs/middleware",children:"Laravel middleware docs"})," for the\nfull feature set, including parameters, terminable middleware, and group\nregistration."]}),"\n",(0,l.jsx)(n.h2,{id:"getting-the-current-user",children:"Getting the current user"}),"\n",(0,l.jsx)(n.pre,{children:(0,l.jsx)(n.code,{className:"language-php",children:"use Illuminate\\Support\\Facades\\Auth;\n\n$user = Auth::user();\n\nif (Auth::check()) {\n // user is logged in\n}\n"})}),"\n",(0,l.jsxs)(n.p,{children:["For API routes protected by ",(0,l.jsx)(n.code,{children:"api.auth"}),", the authenticated user is also available\nvia ",(0,l.jsx)(n.code,{children:"Auth::user()"})," once the middleware has resolved the API key."]})]})}function p(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,l.jsx)(n,{...e,children:(0,l.jsx)(c,{...e})}):c(e)}},28453(e,n,r){r.d(n,{R:()=>o,x:()=>d});var s=r(96540);const l={},t=s.createContext(l);function o(e){const n=s.useContext(t);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function d(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(l):e.components||l:o(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.