PageSourceSearch

https://docs.phpvms.net/assets/js/83e19d64.98d5634d.js

js phpvms.net collected 2026-10-04 13:24:48 UTC 17,205 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkphpvms_docs=self.webpackChunkphpvms_docs||[]).push([[5379],{75083(e,n,s){s.r(n),s.d(n,{assets:()=>t,contentTitle:()=>i,default:()=>h,frontMatter:()=>l,metadata:()=>d,toc:()=>a});const d=JSON.parse('{"id":"developers/addons/overview","title":"Overview","description":"Addons let you ship self-contained features for phpvms \u2014 full pages with their","source":"@site/docs/developers/addons/overview.md","sourceDirName":"developers/addons","slug":"/developers/addons/overview","permalink":"/8.x/developers/addons/overview","draft":false,"unlisted":false,"editUrl":"https://github.com/phpvms/docs/tree/master/docs/developers/addons/overview.md","tags":[],"version":"current","frontMatter":{"id":"overview","title":"Overview"},"sidebar":"docs","previous":{"title":"Environment Configuration","permalink":"/8.x/developers/environment"},"next":{"title":"Publishing to the Addon Registry","permalink":"/8.x/developers/addons/publishing"}}');var r=s(74848),o=s(28453);const l={id:"overview",title:"Overview"},i=void 0,t={},a=[{value:"Why use modules",id:"why-use-modules",level:2},{value:"Quick start",id:"quick-start",level:2},{value:"Naming",id:"naming",level:2},{value:"Generating a module",id:"generating-a-module",level:2},{value:"Directory structure",id:"directory-structure",level:2},{value:"module.json",id:"modulejson",level:2},{value:"Getting module information",id:"getting-module-information",level:2},{value:"Common artisan commands",id:"common-artisan-commands",level:2}];function c(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.p,{children:"Addons let you ship self-contained features for phpvms \u2014 full pages with their\nown URLs, admin screens, API endpoints, migrations, views, and assets \u2014 without\nmodifying core. If you only need a small embeddable component for someone else's\ntemplates, you want a widget instead."}),"\n",(0,r.jsxs)(n.p,{children:["Under the hood, the module system is built on\n",(0,r.jsx)(n.a,{href:"https://laravelmodules.com/docs/13/getting-started/introduction",children:"nWidart/laravel-modules"}),".\nphpvms ships customised stubs so generated modules match phpvms conventions out\nof the box."]}),"\n",(0,r.jsx)(n.h2,{id:"why-use-modules",children:"Why use modules"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Distribution"})," \u2014 modules can be published to the\n",(0,r.jsx)(n.a,{href:"/8.x/developers/addons/publishing",children:"phpvms Addon Registry"})," and installed by operators from the\nadmin UI."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Clarity"})," \u2014 everything related to a feature lives in one folder."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Separation"})," \u2014 modules can be enabled or disabled independently without\ntouching core."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Reusability"})," \u2014 a module can be extracted into a Composer package and shared\nacross phpvms installs."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"quick-start",children:"Quick start"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"# Generate a new module\nphp artisan module:make Sample\n\n# Run its migrations\nphp artisan module:migrate Sample\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Visit ",(0,r.jsx)(n.code,{children:"/sample"})," in your browser \u2014 the default web route and controller are ready\nto go."]}),"\n",(0,r.jsx)(n.admonition,{title:"Reference module",type:"tip",children:(0,r.jsxs)(n.p,{children:["All examples in these docs use a module named ",(0,r.jsx)(n.code,{children:"Sample"}),". The reference source is\n",(0,r.jsx)(n.a,{href:"https://github.com/nabeelio/phpvms-module",children:"nabeelio/phpvms-module on GitHub"}),"."]})}),"\n",(0,r.jsx)(n.h2,{id:"naming",children:"Naming"}),"\n",(0,r.jsxs)(n.p,{children:["The root namespace for your module is ",(0,r.jsx)(n.code,{children:"Modules\\{YourModule}"})," \u2014 for example,\n",(0,r.jsx)(n.code,{children:"Modules\\Sample"}),"."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:["If you are ",(0,r.jsx)(n.code,{children:"Acme"}),", and your module is ",(0,r.jsx)(n.code,{children:"Tours"}),", your module name should be\n",(0,r.jsx)(n.code,{children:"AcmeTours"})]}),"\n",(0,r.jsxs)(n.li,{children:["You also need to set a ",(0,r.jsx)(n.code,{children:"registry_id"})," - for example, this would be\n",(0,r.jsx)(n.code,{children:"acme/tours"}),". You'll need to add this to the ",(0,r.jsx)(n.code,{children:"module.json"})," that's generated."]}),"\n"]}),"\n",(0,r.jsx)(n.admonition,{title:"Pick your author prefix early Your module's database tables must use",type:"warning",children:(0,r.jsxs)(n.p,{children:["a prefix that matches your registry n
1ame/id (e.g. ",(0,r.jsx)(n.code,{children:"acme/reports"})," \u2192 ",(0,r.jsx)(n.code,{children:"acme_*"}),").\nChoose your vendor/author prefix ",(0,r.jsx)(n.strong,{children:"before"})," writing migrations \u2014 changing it\nlater means a destructive migration. See\n",(0,r.jsx)(n.a,{href:"/8.x/developers/addons/database#table-name-convention",children:"Database \u2192 Table name convention"}),"."]})}),"\n",(0,r.jsx)(n.h2,{id:"generating-a-module",children:"Generating a module"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"module:make"})," scaffolds a complete module with controllers, routes, migrations,\nviews, and a service provider. By default it also enables the module \u2014 visit\n",(0,r.jsx)(n.code,{children:"/{module}"})," and your default route resolves immediately."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"php artisan module:make Sample\n"})}),"\n",(0,r.jsx)(n.p,{children:"You can pass several flags to control what gets generated:"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Flag"}),(0,r.jsx)(n.th,{children:"Effect"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"--plain"})," / ",(0,r.jsx)(n.code,{children:"-p"})]}),(0,r.jsx)(n.td,{children:"Minimal scaffold (no controllers, no views)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--api"})}),(0,r.jsxs)(n.td,{children:["API-only \u2014 generates ",(0,r.jsx)(n.code,{children:"routes/api.php"})," and a controller, no views."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"--web"})," (default)"]}),(0,r.jsxs)(n.td,{children:["Full web scaffold with views and ",(0,r.jsx)(n.code,{children:"routes/web.php"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"--disabled"})," / ",(0,r.jsx)(n.code,{children:"-d"})]}),(0,r.jsx)(n.td,{children:"Create the module disabled instead of auto-enabling it."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--force"})}),(0,r.jsx)(n.td,{children:"Overwrite an existing module of the same name."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--author-name="})}),(0,r.jsxs)(n.td,{children:["Sets author name in the generated ",(0,r.jsx)(n.code,{children:"composer.json"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--author-email="})}),(0,r.jsxs)(n.td,{children:["Sets author email in the generated ",(0,r.jsx)(n.code,{children:"composer.json"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"--author-vendor="})}),(0,r.jsx)(n.td,{children:"Sets the Composer vendor segment."})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:"directory-structure",children:"Directory structure"}),"\n",(0,r.jsx)(n.p,{children:"A generated module looks like this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{children:"Modules/Sample/\n\u251c\u2500\u2500 app/\n\u2502   \u251c\u2500\u2500 Http/\n\u2502   \u2502   \u2514\u2500\u2500 Controllers/\n\u2502   \u2502       \u2514\u2500\u2500 SampleController.php\n\u2502   \u251c\u2500\u2500 Models/\n\u2502   \u2514\u2500\u2500 Providers/\n\u2502       \u251c\u2500\u2500 SampleServiceProvider.php\n\u2502       \u2514\u2500\u2500 RouteServiceProvider.php\n\u251c\u2500\u2500 config/\n\u2502   \u2514\u2500\u2500 config.php\n\u251c\u2500\u2500 database/\n\u2502   \u251c\u2500\u2500 factories/\n\u2502   \u251c\u2500\u2500 migrations/\n\u2502   \u2514\u2500\u2500 seeders/\n\u2502       \u2514\u2500\u2500 SampleDatabaseSeeder.php\n\u251c\u2500\u2500 resources/\n\u2502   \u251c\u2500\u2500 assets/\n\u2502   \u2502   \u251c\u2500\u2500 js/\n\u2502   \u2502   \u2514\u2500\u2500 sass/\n\u2502   \u2514\u2500\u2500 views/\n\u2502       \u251c\u2500\u2500 layouts/master.blade.php\n\u2502       \u2514\u2500\u2500 index.blade.php\n\u251c\u2500\u2500 routes/\n\u2502   \u251c\u2500\u2500 api.php\n\u2502   \u2514\u2500\u2500 web.php\n\u251c\u2500\u2500 tests/\n\u2502   \u251c\u2500\u2500 Feature/\n\u2502   \u2514\u2500\u2500 Unit/\n\u251c\u2500\u2500 composer.json\n\u251c\u2500\u2500 module.json\n\u251c\u2500\u2500 package.json\n\u2514\u2500\u2500 vite.config.js\n"})}),"\n",(0,r.jsx)(n.p,{children:"A few notes:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"app/"})})," holds all your PHP source \u2014 controllers, models, services,\nlisteners, providers. The PSR-4 root maps ",(0,r.jsx)(n.code,{children:"Modules\\AcmeSample\\"})," to this\ndirectory."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"config/config.php"})})," is published to ",(0,r.jsx)(n.code,{children:"config/sample.php"})," in the host app.\nAccess values with ",(0,r.jsx)(n.code,{children:"config('sample.option_name')"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"database/migrations/"})})," holds your migrations (see\n",(0,r.jsx)(n.a,{href:"/8.x/developers/addons/database",children:"Database"}),"). When an operator visits ",(0,r.jsx)(n.code,{children:"/update"}),", pending\nmigrations from all enabled modules are run automatically."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"resources/views/"})})," holds Blade views, accessed as ",(0,r.jsx)(n.code,{children:"view('sample::index')"}),".\nSee ",(0,r.jsx)(n.a,{href:"/8.x/developers/addons/templating",children:"Templating"}),"."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"routes/web.php"})})," and ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"routes/api.php"})})," define the module's URLs. See\n",(0,r.jsx)(n.a,{href:"/8.x/developers/addons/routing-controllers",children:"Routing & Controllers"}),"."]}),"\n"]}),"\n",(0,r.jsx)(n.h2,{id:"modulejson",children:"module.json"}),"\n",(0,r.jsx)(n.p,{children:"The metadata for your module. Generated automatically; you'll only edit it if\nyou need to change descriptors:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n  "name": "AcmeSample",\n  "alias": "acme_sample",\n  "registry_id": "acme/sample",\n  "description": "Sample module for phpvms",\n  "keywords": [],\n  "priority": 0,\n  "providers": ["Modules\\\\AcmeSample\\\\Providers\\\\AcmeSampleServiceProvider"],\n  "files": []\n}\n'})}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Field"}),(0,r.jsx)(n.th,{children:"Purpose"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"name"})}),(0,r.jsx)(n.td,{children:"StudlyCase module name. Used as the directory name and in class namespaces."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"alias"})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.strong,{children:"Lowercase"})," namespace for views, config, and translations. ",(0,r.jsx)(n.code,{children:"view('sample::index')"}),"."]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"registry_id"})}),(0,r.jsx)(n.td,{children:"Unique identifier for the module in the registry."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"description"})}),(0,r.jsx)(n.td,{children:"Short human-readable description."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"keywords"})}),(0,r.jsx)(n.td,{children:"Optional array of search keywords (used by the registry)."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"priority"})}),(0,r.jsx)(n.td,{children:"Integer load order. Lower numbers boot first. Use to enforce inter-module dependencies."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"providers"})}),(0,r.jsx)(n.td,{children:"Service providers to register. Add additional providers here if you create more."})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"files"})}),(0,r.jsxs)(n.td,{children:["Extra files to autoload on boot \u2014 e.g. ",(0,r.jsx)(n.code,{children:'["app/helpers.php"]'})," for global helper functions."]})]})]})]}),"\n",(0,r.jsx)(n.h2,{id:"getting-module-information",children:"Getting module information"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"Module"})," facade lets you query and manipulate modules at runtime. Once you\nhave a module instance, you can introspect its metadata:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-php",children:"$module = Module::find('acme_sample');\n\n$module->getName();            // 'Sample'\n$module->getLowerName();       // 'sample'\n$module->getDescription();\n$module->getPriority();\n$module->get('alias');         // any module.json key\n$module->getPath();            // absolute path on disk\n$module->getAppPath();         // path to app/\n$module->isEnabled();\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"module_path()"})," helper resolves paths inside a module without needing a\n",(0,r.jsx)(n.code,{children:"Module"})," instance:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-php",children:"module_path('AcmeSample', 'resources/views');\n// \u2192 /var/www/phpvms/Modules/AcmeSample/resources/views\n"})}),"\n",(0,r.jsxs)(n.p,{children:["See the\n",(0,r.jsx)(n.a,{href:"https://laravelmodules.com/docs/13/advanced-tools/facade
1-methods",children:"Module facade reference"}),"\nand\n",(0,r.jsx)(n.a,{href:"https://laravelmodules.com/docs/13/advanced-tools/module-methods",children:"Module instance reference"}),"\nfor the full surface."]}),"\n",(0,r.jsx)(n.h2,{id:"common-artisan-commands",children:"Common artisan commands"}),"\n",(0,r.jsxs)(n.p,{children:["A few you'll use often. The full list lives in the\n",(0,r.jsx)(n.a,{href:"https://laravelmodules.com/docs/13/advanced-tools/artisan-commands",children:"laravel-modules artisan reference"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-shell",children:"# Module lifecycle\nphp artisan module:list\nphp artisan module:enable AcmeSample\nphp artisan module:disable AcmeSample\nphp artisan module:delete AcmeSample\n\n# Generators (see the relevant doc page for details)\nphp artisan module:make-controller AcmeSample PostController\nphp artisan module:make-model AcmeSample Post --migration --factory\nphp artisan module:make-migration create_acme_sample_posts_table AcmeSample\nphp artisan module:make-seed AcmeSamplePostsSeeder AcmeSample\nphp artisan module:make-view posts.index AcmeSample\nphp artisan module:make-component Alert AcmeSample\nphp artisan module:make-middleware EnsurePostIsPublished AcmeSample\nphp artisan module:make-event PostWasCreated AcmeSample\nphp artisan module:make-listener NotifyAdmin AcmeSample --event=PostWasCreated\n\n# Migrations\nphp artisan module:migrate AcmeSample              # run pending\nphp artisan module:migrate-rollback AcmeSample\nphp artisan module:migrate-refresh AcmeSample --seed\nphp artisan module:seed AcmeSample\n\n# Maintenance\nphp artisan module:dump AcmeSample                 # regenerate autoload\nphp artisan module:composer-update AcmeSample      # composer update inside the module\nphp artisan module:show-model AcmeSample           # inspect models\n"})})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(c,{...e})}):c(e)}},28453(e,n,s){s.d(n,{R:()=>l,x:()=>i});var d=s(96540);const r={},o=d.createContext(r);function l(e){const n=d.useContext(o);return d.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:l(e.components),d.createElement(o.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.