├── .github └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── README.md ├── composer.json ├── composer.lock ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── phpunit.xml ├── routes └── inertia-components-routes.php ├── src ├── .DS_Store ├── Attributes │ ├── Always.php │ ├── Deferred.php │ ├── Http │ │ ├── DeleteAction.php │ │ ├── GetAction.php │ │ ├── PatchAction.php │ │ ├── PostAction.php │ │ └── PutAction.php │ └── Lazy.php ├── Contacts │ └── HttpActionContract.php ├── Data │ └── ComponentMeta.php ├── Exceptions │ └── MissingHttpMethodException.php ├── InertiaComponent.php ├── Providers │ └── InertiaComponentsServiceProvider.php └── Services │ └── RouteRegistrationProxy.php └── tests ├── Feature ├── InertiaTest.php └── NonHttpTests.php ├── Http └── Inertia │ ├── ActionRoutes.php │ ├── Basic.php │ └── ParamComponent.php ├── InertiaComponentsServiceProviderTest.php ├── Pest.php ├── Providers └── TestApplicationServiceProvider.php ├── TestApp └── resources │ └── views │ └── app.blade.php ├── TestCase.php └── Unit └── ExampleTest.php /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | run-tests: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | php: [8.3, 8.2] 12 | laravel: [10.*, 11.*] 13 | testbench: [^8.0, ^9.0] 14 | dependency-version: [prefer-lowest, prefer-stable] 15 | include: 16 | - laravel: 10.* 17 | testbench: ^8.0 18 | - laravel: 11.* 19 | testbench: ^9.0 20 | exclude: 21 | - laravel: 11.0 22 | testbench: ^8.0 23 | - laravel: 10.0 24 | testbench: ^9.0 25 | # - dependency-version: prefer-stable 26 | # testbench: ^9.0 27 | 28 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} 29 | 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php }} 38 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick 39 | coverage: none 40 | 41 | - name: Setup Problem Matches 42 | run: | 43 | echo "::add-matcher::${{ runner.tool_cache }}/php.json" 44 | echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 45 | 46 | - name: Install dependencies 47 | run: | 48 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "nesbot/carbon:>=2.62.1" --no-interaction --no-update 49 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction 50 | 51 | - name: Execute tests 52 | run: vendor/bin/pest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .phpunit.result.cache 3 | vendor 4 | node_modules 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Inertia Components 2 | 3 | > [!WARNING] 4 | > This package is in a pre-release state and the API may change. 5 | 6 | This package allows you to create Livewire-style class components for your InertiaJS applications. 7 | 8 | ## Features: 9 | 10 | - ✅ Define HTTP-named resourceful style methods for `show`/`store`/`update`/`destroy` right in the same class. 11 | - ✅ Any `public` properties or methods pass data back to your components. 12 | - ✅ Attributes to mark public methods as `#[Lazy]` or `#[Always]` 13 | - ✅ Create methods that auto-wire their own routes by using the `#[PostAction]`, `#[GetAction]` etc Attributes. 14 | - ⌛ Create components with `php artisan make:inertia` 15 | - ✅ `Route::inertia` helper. 16 | 17 | ## Why? 18 | 19 | - Better organisation and encapsulation of code. 20 | - Reduce clutter in your route files. 21 | 22 | ## HTTP/Resourceful Methods. 23 | 24 | The base class reserves four `public` method names to use and their corresponding HTTP methods: 25 | 26 | | Method Name | HTTP Method | 27 | |-------------| ----- | 28 | | show | GET | 29 | | store | POST | 30 | | update | PATCH/PUT | 31 | | destroy | DELETE | 32 | 33 | Each of these methods behaves just like a controller method should, so you can inject route parameters and dependencies as needed: 34 | 35 | ```php 36 | public function show(Request $request, string $id, ModelName $model): array {} 37 | ``` 38 | 39 | ### Returning data from `show()` 40 | 41 | The `show` method is used when your route is hit with a `GET` request. 42 | 43 | If you return an `array` from this method, it will be merged with the rest of the public properties and methods, you can also 44 | use the usual `Inertia::lazy` and `Inertia::always` closures from here too. 45 | 46 | ## Autowiring HTTP Methods. 47 | 48 | If you need to call another route that's non-RESTful or tangential to the resource you're showing, you can use autowiring 49 | methods, these will register the route for you with either an automatic or user-given URL. 50 | 51 | For example, showing a list of blog posts on your `show` route, but want the ability to send a quick AJAX request to toggle 52 | the post as published or unpublished? 53 | 54 | Setup a function in your component, and add the appropriate HTTP method action attribute to it: 55 | 56 | ```php 57 | #[PatchAction(url:'/{post}/toggle-published')] 58 | function togglePublished(BlogPost $post) { 59 | $post->published = !$post->published; 60 | $post->save(); 61 | return redirect()->back(); 62 | } 63 | ``` 64 | 65 | Assuming your component was registered with a path like `/posts`, this would automatically register a route at 66 | `POST posts/{post}/toggle-published` for you. 67 | 68 | > Note: if you use the `#[GetAction]` attribute, you shouldn't return an Inertia::response() from it - if you want to do that, use another 69 | > fully registered route instead. 70 | 71 | ## Full (WIP) Example: 72 | 73 | ```php 74 | =18" 21 | }, 22 | "funding": { 23 | "url": "https://github.com/sponsors/sindresorhus" 24 | } 25 | }, 26 | "node_modules/ansi-regex": { 27 | "version": "6.0.1", 28 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", 29 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", 30 | "dev": true, 31 | "engines": { 32 | "node": ">=12" 33 | }, 34 | "funding": { 35 | "url": "https://github.com/chalk/ansi-regex?sponsor=1" 36 | } 37 | }, 38 | "node_modules/ansi-styles": { 39 | "version": "6.2.1", 40 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 41 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 42 | "dev": true, 43 | "engines": { 44 | "node": ">=12" 45 | }, 46 | "funding": { 47 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 48 | } 49 | }, 50 | "node_modules/braces": { 51 | "version": "3.0.3", 52 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 53 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 54 | "dev": true, 55 | "dependencies": { 56 | "fill-range": "^7.1.1" 57 | }, 58 | "engines": { 59 | "node": ">=8" 60 | } 61 | }, 62 | "node_modules/chalk": { 63 | "version": "5.3.0", 64 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", 65 | "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", 66 | "dev": true, 67 | "engines": { 68 | "node": "^12.17.0 || ^14.13 || >=16.0.0" 69 | }, 70 | "funding": { 71 | "url": "https://github.com/chalk/chalk?sponsor=1" 72 | } 73 | }, 74 | "node_modules/cli-cursor": { 75 | "version": "5.0.0", 76 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", 77 | "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", 78 | "dev": true, 79 | "dependencies": { 80 | "restore-cursor": "^5.0.0" 81 | }, 82 | "engines": { 83 | "node": ">=18" 84 | }, 85 | "funding": { 86 | "url": "https://github.com/sponsors/sindresorhus" 87 | } 88 | }, 89 | "node_modules/cli-truncate": { 90 | "version": "4.0.0", 91 | "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", 92 | "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", 93 | "dev": true, 94 | "dependencies": { 95 | "slice-ansi": "^5.0.0", 96 | "string-width": "^7.0.0" 97 | }, 98 | "engines": { 99 | "node": ">=18" 100 | }, 101 | "funding": { 102 | "url": "https://github.com/sponsors/sindresorhus" 103 | } 104 | }, 105 | "node_modules/colorette": { 106 | "version": "2.0.20", 107 | "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", 108 | "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", 109 | "dev": true 110 | }, 111 | "node_modules/commander": { 112 | "version": "12.1.0", 113 | "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", 114 | "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", 115 | "dev": true, 116 | "engines": { 117 | "node": ">=18" 118 | } 119 | }, 120 | "node_modules/cross-spawn": { 121 | "version": "7.0.3", 122 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 123 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 124 | "dev": true, 125 | "dependencies": { 126 | "path-key": "^3.1.0", 127 | "shebang-command": "^2.0.0", 128 | "which": "^2.0.1" 129 | }, 130 | "engines": { 131 | "node": ">= 8" 132 | } 133 | }, 134 | "node_modules/debug": { 135 | "version": "4.3.6", 136 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 137 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 138 | "dev": true, 139 | "dependencies": { 140 | "ms": "2.1.2" 141 | }, 142 | "engines": { 143 | "node": ">=6.0" 144 | }, 145 | "peerDependenciesMeta": { 146 | "supports-color": { 147 | "optional": true 148 | } 149 | } 150 | }, 151 | "node_modules/emoji-regex": { 152 | "version": "10.4.0", 153 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", 154 | "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", 155 | "dev": true 156 | }, 157 | "node_modules/environment": { 158 | "version": "1.1.0", 159 | "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", 160 | "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", 161 | "dev": true, 162 | "engines": { 163 | "node": ">=18" 164 | }, 165 | "funding": { 166 | "url": "https://github.com/sponsors/sindresorhus" 167 | } 168 | }, 169 | "node_modules/eventemitter3": { 170 | "version": "5.0.1", 171 | "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", 172 | "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", 173 | "dev": true 174 | }, 175 | "node_modules/execa": { 176 | "version": "8.0.1", 177 | "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", 178 | "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", 179 | "dev": true, 180 | "dependencies": { 181 | "cross-spawn": "^7.0.3", 182 | "get-stream": "^8.0.1", 183 | "human-signals": "^5.0.0", 184 | "is-stream": "^3.0.0", 185 | "merge-stream": "^2.0.0", 186 | "npm-run-path": "^5.1.0", 187 | "onetime": "^6.0.0", 188 | "signal-exit": "^4.1.0", 189 | "strip-final-newline": "^3.0.0" 190 | }, 191 | "engines": { 192 | "node": ">=16.17" 193 | }, 194 | "funding": { 195 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 196 | } 197 | }, 198 | "node_modules/fill-range": { 199 | "version": "7.1.1", 200 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 201 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 202 | "dev": true, 203 | "dependencies": { 204 | "to-regex-range": "^5.0.1" 205 | }, 206 | "engines": { 207 | "node": ">=8" 208 | } 209 | }, 210 | "node_modules/get-east-asian-width": { 211 | "version": "1.2.0", 212 | "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", 213 | "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", 214 | "dev": true, 215 | "engines": { 216 | "node": ">=18" 217 | }, 218 | "funding": { 219 | "url": "https://github.com/sponsors/sindresorhus" 220 | } 221 | }, 222 | "node_modules/get-stream": { 223 | "version": "8.0.1", 224 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", 225 | "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", 226 | "dev": true, 227 | "engines": { 228 | "node": ">=16" 229 | }, 230 | "funding": { 231 | "url": "https://github.com/sponsors/sindresorhus" 232 | } 233 | }, 234 | "node_modules/human-signals": { 235 | "version": "5.0.0", 236 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", 237 | "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", 238 | "dev": true, 239 | "engines": { 240 | "node": ">=16.17.0" 241 | } 242 | }, 243 | "node_modules/is-fullwidth-code-point": { 244 | "version": "4.0.0", 245 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", 246 | "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", 247 | "dev": true, 248 | "engines": { 249 | "node": ">=12" 250 | }, 251 | "funding": { 252 | "url": "https://github.com/sponsors/sindresorhus" 253 | } 254 | }, 255 | "node_modules/is-number": { 256 | "version": "7.0.0", 257 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 258 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 259 | "dev": true, 260 | "engines": { 261 | "node": ">=0.12.0" 262 | } 263 | }, 264 | "node_modules/is-stream": { 265 | "version": "3.0.0", 266 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", 267 | "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", 268 | "dev": true, 269 | "engines": { 270 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 271 | }, 272 | "funding": { 273 | "url": "https://github.com/sponsors/sindresorhus" 274 | } 275 | }, 276 | "node_modules/isexe": { 277 | "version": "2.0.0", 278 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 279 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 280 | "dev": true 281 | }, 282 | "node_modules/lilconfig": { 283 | "version": "3.1.2", 284 | "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", 285 | "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", 286 | "dev": true, 287 | "engines": { 288 | "node": ">=14" 289 | }, 290 | "funding": { 291 | "url": "https://github.com/sponsors/antonk52" 292 | } 293 | }, 294 | "node_modules/lint-staged": { 295 | "version": "15.2.9", 296 | "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz", 297 | "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==", 298 | "dev": true, 299 | "dependencies": { 300 | "chalk": "~5.3.0", 301 | "commander": "~12.1.0", 302 | "debug": "~4.3.6", 303 | "execa": "~8.0.1", 304 | "lilconfig": "~3.1.2", 305 | "listr2": "~8.2.4", 306 | "micromatch": "~4.0.7", 307 | "pidtree": "~0.6.0", 308 | "string-argv": "~0.3.2", 309 | "yaml": "~2.5.0" 310 | }, 311 | "bin": { 312 | "lint-staged": "bin/lint-staged.js" 313 | }, 314 | "engines": { 315 | "node": ">=18.12.0" 316 | }, 317 | "funding": { 318 | "url": "https://opencollective.com/lint-staged" 319 | } 320 | }, 321 | "node_modules/listr2": { 322 | "version": "8.2.4", 323 | "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", 324 | "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", 325 | "dev": true, 326 | "dependencies": { 327 | "cli-truncate": "^4.0.0", 328 | "colorette": "^2.0.20", 329 | "eventemitter3": "^5.0.1", 330 | "log-update": "^6.1.0", 331 | "rfdc": "^1.4.1", 332 | "wrap-ansi": "^9.0.0" 333 | }, 334 | "engines": { 335 | "node": ">=18.0.0" 336 | } 337 | }, 338 | "node_modules/log-update": { 339 | "version": "6.1.0", 340 | "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", 341 | "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", 342 | "dev": true, 343 | "dependencies": { 344 | "ansi-escapes": "^7.0.0", 345 | "cli-cursor": "^5.0.0", 346 | "slice-ansi": "^7.1.0", 347 | "strip-ansi": "^7.1.0", 348 | "wrap-ansi": "^9.0.0" 349 | }, 350 | "engines": { 351 | "node": ">=18" 352 | }, 353 | "funding": { 354 | "url": "https://github.com/sponsors/sindresorhus" 355 | } 356 | }, 357 | "node_modules/log-update/node_modules/is-fullwidth-code-point": { 358 | "version": "5.0.0", 359 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", 360 | "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", 361 | "dev": true, 362 | "dependencies": { 363 | "get-east-asian-width": "^1.0.0" 364 | }, 365 | "engines": { 366 | "node": ">=18" 367 | }, 368 | "funding": { 369 | "url": "https://github.com/sponsors/sindresorhus" 370 | } 371 | }, 372 | "node_modules/log-update/node_modules/slice-ansi": { 373 | "version": "7.1.0", 374 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", 375 | "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", 376 | "dev": true, 377 | "dependencies": { 378 | "ansi-styles": "^6.2.1", 379 | "is-fullwidth-code-point": "^5.0.0" 380 | }, 381 | "engines": { 382 | "node": ">=18" 383 | }, 384 | "funding": { 385 | "url": "https://github.com/chalk/slice-ansi?sponsor=1" 386 | } 387 | }, 388 | "node_modules/merge-stream": { 389 | "version": "2.0.0", 390 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 391 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 392 | "dev": true 393 | }, 394 | "node_modules/micromatch": { 395 | "version": "4.0.8", 396 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", 397 | "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", 398 | "dev": true, 399 | "dependencies": { 400 | "braces": "^3.0.3", 401 | "picomatch": "^2.3.1" 402 | }, 403 | "engines": { 404 | "node": ">=8.6" 405 | } 406 | }, 407 | "node_modules/mimic-fn": { 408 | "version": "4.0.0", 409 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", 410 | "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", 411 | "dev": true, 412 | "engines": { 413 | "node": ">=12" 414 | }, 415 | "funding": { 416 | "url": "https://github.com/sponsors/sindresorhus" 417 | } 418 | }, 419 | "node_modules/mimic-function": { 420 | "version": "5.0.1", 421 | "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", 422 | "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", 423 | "dev": true, 424 | "engines": { 425 | "node": ">=18" 426 | }, 427 | "funding": { 428 | "url": "https://github.com/sponsors/sindresorhus" 429 | } 430 | }, 431 | "node_modules/ms": { 432 | "version": "2.1.2", 433 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 434 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 435 | "dev": true 436 | }, 437 | "node_modules/npm-run-path": { 438 | "version": "5.3.0", 439 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", 440 | "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", 441 | "dev": true, 442 | "dependencies": { 443 | "path-key": "^4.0.0" 444 | }, 445 | "engines": { 446 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 447 | }, 448 | "funding": { 449 | "url": "https://github.com/sponsors/sindresorhus" 450 | } 451 | }, 452 | "node_modules/npm-run-path/node_modules/path-key": { 453 | "version": "4.0.0", 454 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 455 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 456 | "dev": true, 457 | "engines": { 458 | "node": ">=12" 459 | }, 460 | "funding": { 461 | "url": "https://github.com/sponsors/sindresorhus" 462 | } 463 | }, 464 | "node_modules/onetime": { 465 | "version": "6.0.0", 466 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", 467 | "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", 468 | "dev": true, 469 | "dependencies": { 470 | "mimic-fn": "^4.0.0" 471 | }, 472 | "engines": { 473 | "node": ">=12" 474 | }, 475 | "funding": { 476 | "url": "https://github.com/sponsors/sindresorhus" 477 | } 478 | }, 479 | "node_modules/path-key": { 480 | "version": "3.1.1", 481 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 482 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 483 | "dev": true, 484 | "engines": { 485 | "node": ">=8" 486 | } 487 | }, 488 | "node_modules/picomatch": { 489 | "version": "2.3.1", 490 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 491 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 492 | "dev": true, 493 | "engines": { 494 | "node": ">=8.6" 495 | }, 496 | "funding": { 497 | "url": "https://github.com/sponsors/jonschlinkert" 498 | } 499 | }, 500 | "node_modules/pidtree": { 501 | "version": "0.6.0", 502 | "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", 503 | "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", 504 | "dev": true, 505 | "bin": { 506 | "pidtree": "bin/pidtree.js" 507 | }, 508 | "engines": { 509 | "node": ">=0.10" 510 | } 511 | }, 512 | "node_modules/restore-cursor": { 513 | "version": "5.1.0", 514 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", 515 | "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", 516 | "dev": true, 517 | "dependencies": { 518 | "onetime": "^7.0.0", 519 | "signal-exit": "^4.1.0" 520 | }, 521 | "engines": { 522 | "node": ">=18" 523 | }, 524 | "funding": { 525 | "url": "https://github.com/sponsors/sindresorhus" 526 | } 527 | }, 528 | "node_modules/restore-cursor/node_modules/onetime": { 529 | "version": "7.0.0", 530 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", 531 | "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", 532 | "dev": true, 533 | "dependencies": { 534 | "mimic-function": "^5.0.0" 535 | }, 536 | "engines": { 537 | "node": ">=18" 538 | }, 539 | "funding": { 540 | "url": "https://github.com/sponsors/sindresorhus" 541 | } 542 | }, 543 | "node_modules/rfdc": { 544 | "version": "1.4.1", 545 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", 546 | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", 547 | "dev": true 548 | }, 549 | "node_modules/shebang-command": { 550 | "version": "2.0.0", 551 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 552 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 553 | "dev": true, 554 | "dependencies": { 555 | "shebang-regex": "^3.0.0" 556 | }, 557 | "engines": { 558 | "node": ">=8" 559 | } 560 | }, 561 | "node_modules/shebang-regex": { 562 | "version": "3.0.0", 563 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 564 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 565 | "dev": true, 566 | "engines": { 567 | "node": ">=8" 568 | } 569 | }, 570 | "node_modules/signal-exit": { 571 | "version": "4.1.0", 572 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 573 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 574 | "dev": true, 575 | "engines": { 576 | "node": ">=14" 577 | }, 578 | "funding": { 579 | "url": "https://github.com/sponsors/isaacs" 580 | } 581 | }, 582 | "node_modules/slice-ansi": { 583 | "version": "5.0.0", 584 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", 585 | "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", 586 | "dev": true, 587 | "dependencies": { 588 | "ansi-styles": "^6.0.0", 589 | "is-fullwidth-code-point": "^4.0.0" 590 | }, 591 | "engines": { 592 | "node": ">=12" 593 | }, 594 | "funding": { 595 | "url": "https://github.com/chalk/slice-ansi?sponsor=1" 596 | } 597 | }, 598 | "node_modules/string-argv": { 599 | "version": "0.3.2", 600 | "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", 601 | "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", 602 | "dev": true, 603 | "engines": { 604 | "node": ">=0.6.19" 605 | } 606 | }, 607 | "node_modules/string-width": { 608 | "version": "7.2.0", 609 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", 610 | "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", 611 | "dev": true, 612 | "dependencies": { 613 | "emoji-regex": "^10.3.0", 614 | "get-east-asian-width": "^1.0.0", 615 | "strip-ansi": "^7.1.0" 616 | }, 617 | "engines": { 618 | "node": ">=18" 619 | }, 620 | "funding": { 621 | "url": "https://github.com/sponsors/sindresorhus" 622 | } 623 | }, 624 | "node_modules/strip-ansi": { 625 | "version": "7.1.0", 626 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 627 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 628 | "dev": true, 629 | "dependencies": { 630 | "ansi-regex": "^6.0.1" 631 | }, 632 | "engines": { 633 | "node": ">=12" 634 | }, 635 | "funding": { 636 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" 637 | } 638 | }, 639 | "node_modules/strip-final-newline": { 640 | "version": "3.0.0", 641 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", 642 | "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", 643 | "dev": true, 644 | "engines": { 645 | "node": ">=12" 646 | }, 647 | "funding": { 648 | "url": "https://github.com/sponsors/sindresorhus" 649 | } 650 | }, 651 | "node_modules/to-regex-range": { 652 | "version": "5.0.1", 653 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 654 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 655 | "dev": true, 656 | "dependencies": { 657 | "is-number": "^7.0.0" 658 | }, 659 | "engines": { 660 | "node": ">=8.0" 661 | } 662 | }, 663 | "node_modules/which": { 664 | "version": "2.0.2", 665 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 666 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 667 | "dev": true, 668 | "dependencies": { 669 | "isexe": "^2.0.0" 670 | }, 671 | "bin": { 672 | "node-which": "bin/node-which" 673 | }, 674 | "engines": { 675 | "node": ">= 8" 676 | } 677 | }, 678 | "node_modules/wrap-ansi": { 679 | "version": "9.0.0", 680 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", 681 | "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", 682 | "dev": true, 683 | "dependencies": { 684 | "ansi-styles": "^6.2.1", 685 | "string-width": "^7.0.0", 686 | "strip-ansi": "^7.1.0" 687 | }, 688 | "engines": { 689 | "node": ">=18" 690 | }, 691 | "funding": { 692 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 693 | } 694 | }, 695 | "node_modules/yaml": { 696 | "version": "2.5.0", 697 | "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", 698 | "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", 699 | "dev": true, 700 | "bin": { 701 | "yaml": "bin.mjs" 702 | }, 703 | "engines": { 704 | "node": ">= 14" 705 | } 706 | } 707 | } 708 | } 709 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "lint-staged": "^15.2.9" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | ./app 15 | ./src 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /routes/inertia-components-routes.php: -------------------------------------------------------------------------------- 1 | name('inertia-components.index'); 6 | // Route::get('/inertia-components/create', [InertiaComponentsController::class, 'create'])->name('inertia-components.create'); 7 | // Route::post('/inertia-components', [InertiaComponentsController::class, 'store'])->name('inertia-components.store'); 8 | // Route::get('/inertia-components/{inertia-component}', [InertiaComponentsController::class, 'show'])->name('inertia-components.show'); 9 | // Route::get('/inertia-components/{inertia-component}/edit', [InertiaComponentsController::class, 'edit'])->name('inertia-components.edit'); 10 | // Route::put('/inertia-components/{inertia-component}', [InertiaComponentsController::class, 'update'])->name('inertia-components.update'); 11 | // Route::delete('/inertia-components/{inertia-component}', [InertiaComponentsController::class, 'destroy'])->name('inertia-components.destroy'); 12 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intrfce/laravel-inertia-components/00d427ae6609c382834ac08b927dbd55fd65bfb8/src/.DS_Store -------------------------------------------------------------------------------- /src/Attributes/Always.php: -------------------------------------------------------------------------------- 1 | getMethods(ReflectionMethod::IS_PUBLIC)) 46 | ->reject(fn (ReflectionMethod $method) => str_starts_with($method->getName(), '__')) 47 | ->reject(fn (ReflectionMethod $method) => in_array($method->getName(), self::$reservedMethodNames)) 48 | ->keyBy(fn (ReflectionMethod $method) => $method->getName()) 49 | ->flatMap(function (ReflectionMethod $method) { 50 | return collect([GetAction::class, PostAction::class, PutAction::class, PatchAction::class, DeleteAction::class]) 51 | ->map(function ($methodAttributeClass) use ($method) { 52 | $methodAttributes = $method->getAttributes($methodAttributeClass); 53 | if (! empty($methodAttributes[0])) { 54 | 55 | $methodClass = $methodAttributes[0]->getName(); 56 | /** @var HttpActionContract $method */ 57 | $httpMethod = (new $methodClass)->method(); 58 | $url = Str::kebab($method->getName()); 59 | 60 | return [ 61 | 'method' => strtolower($httpMethod), 62 | 'path' => $url, 63 | 'name' => Str::snake($url, '_'), 64 | 'target_function' => $method->getName(), 65 | 'target_class' => static::class, 66 | ]; 67 | } else { 68 | return null; 69 | } 70 | }) 71 | ->values(); 72 | }) 73 | ->filter() 74 | ->all(); 75 | } 76 | 77 | /** 78 | * @throws \Exception 79 | */ 80 | protected function getTemplate(): string 81 | { 82 | if (!isset($this->template)) { 83 | throw new \Exception('No $template property has been set for this component'); 84 | } 85 | return $this->template; 86 | } 87 | 88 | /** 89 | * @throws MissingHttpMethodException 90 | */ 91 | public function showProxy(): mixed 92 | { 93 | return $this->buildResponse('show'); 94 | } 95 | 96 | /** 97 | * @throws MissingHttpMethodException 98 | */ 99 | public function storeProxy(): mixed 100 | { 101 | return $this->buildResponse('store'); 102 | } 103 | 104 | /** 105 | * @throws MissingHttpMethodException 106 | */ 107 | public function updateProxy(): mixed 108 | { 109 | return $this->buildResponse('udpate'); 110 | } 111 | 112 | /** 113 | * @throws MissingHttpMethodException 114 | */ 115 | public function destroyProxy(): mixed 116 | { 117 | return $this->buildResponse('destroy'); 118 | } 119 | 120 | /** 121 | * @throws MissingHttpMethodException 122 | * @throws \Exception 123 | */ 124 | protected function buildResponse(string $resource): mixed 125 | { 126 | $response = $this->callMethodIfExists($resource); 127 | 128 | // Instance of anything except an array or a collection? return it. 129 | if (! is_array($response)) { 130 | return $response; 131 | } 132 | 133 | // Use reflection to get ALL the `public` properties and methods, but exclude our known ones. 134 | $reflectionClass = new ReflectionClass($this); 135 | 136 | $publicProperties = collect($reflectionClass->getProperties(ReflectionProperty::IS_PUBLIC)); 137 | $publicPropertyNames = $publicProperties->map(fn (ReflectionProperty $property) => $property->getName()); 138 | $propertyValues = collect($reflectionClass->getDefaultProperties())->filter(fn ($value, $name) => $publicPropertyNames->contains($name)); 139 | 140 | $publicMethods = collect($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC)) 141 | ->reject(fn (ReflectionMethod $method) => str_starts_with($method->getName(), '__')) 142 | ->reject(fn (ReflectionMethod $method) => in_array($method->getName(), self::$reservedMethodNames)) 143 | ->keyBy(fn (ReflectionMethod $method) => $method->getName()) 144 | ->map(function (ReflectionMethod $method) { 145 | if ($this->isPropertyMethodLazy($method->getName())) { 146 | return Inertia::lazy([$this, $method->getName()]); 147 | } 148 | if ($this->isPropertyMethodAlways($method->getName())) { 149 | return Inertia::always([$this, $method->getName()]); 150 | } 151 | 152 | return App::call([$this, $method->getName()]); 153 | }); 154 | 155 | $merged = $publicMethods 156 | ->merge($propertyValues) 157 | ->merge(collect($response)) 158 | ->merge(['component' => new ComponentMeta( 159 | request()->url(), 160 | )]) 161 | ->toArray(); 162 | 163 | return Inertia::render($this->getTemplate(), $merged); 164 | } 165 | 166 | /** 167 | * @throws MissingHttpMethodException 168 | */ 169 | private function callMethodIfExists(string $method_name): mixed 170 | { 171 | if (method_exists($this, $method_name)) { 172 | return App::call([$this, $method_name], [...request()->route()->parameters()]); 173 | } 174 | throw new MissingHttpMethodException("Method {$method_name} does not exist."); 175 | } 176 | 177 | /** 178 | * Checks is a property method is marked as Lazy, which wraps it in an 179 | * Inertia::lazy call. 180 | */ 181 | private function isPropertyMethodLazy(string $method_name): bool 182 | { 183 | $reflectionClass = new ReflectionClass($this); 184 | $attributes = $reflectionClass->getMethod($method_name)->getAttributes(Lazy::class); 185 | 186 | return isset($attributes[0]); 187 | } 188 | 189 | private function isPropertyMethodAlways(string $method_name): bool 190 | { 191 | $reflectionClass = new ReflectionClass($this); 192 | $attributes = $reflectionClass->getMethod($method_name)->getAttributes(Always::class); 193 | 194 | return isset($attributes[0]); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/Providers/InertiaComponentsServiceProvider.php: -------------------------------------------------------------------------------- 1 | resources); 35 | $this->resources = collect($toRegister) 36 | ->unique() 37 | ->each(function ($route) use ($accepts) { 38 | if (! $accepts->contains($route)) { 39 | throw new Exception("The route method '{$route}' is not recognised, only " . $accepts->join(',', 'and') . ' are accepted'); 40 | } 41 | }) 42 | ->push('show') // Always has to be there. 43 | ->toArray(); 44 | 45 | return $this; 46 | } 47 | 48 | public function name(string $name): self 49 | { 50 | $this->baseName = $name; 51 | 52 | return $this; 53 | } 54 | 55 | public function __destruct() 56 | { 57 | foreach ($this->resources as $resource) { 58 | 59 | $baseDefinition = match ($resource) { 60 | 'show' => RouteFacade::get($this->path, [$this->classComponent, 'showProxy']), 61 | 'store' => RouteFacade::post($this->path, [$this->classComponent, 'storeProxy']), 62 | 'update' => RouteFacade::patch($this->path, [$this->classComponent, 'updateProxy']), 63 | 'destroy' => RouteFacade::delete($this->path, [$this->classComponent, 'destroyProxy']), 64 | }; 65 | 66 | if ($this->baseName !== null) { 67 | $baseDefinition->name("{$this->baseName}.{$resource}"); 68 | } 69 | 70 | if (! empty($this->proxiedMethodCalls)) { 71 | foreach ($this->proxiedMethodCalls as $method => $params) { 72 | $baseDefinition = call_user_func_array($baseDefinition, $params); 73 | } 74 | } 75 | } 76 | 77 | // Register any action routes. 78 | foreach (call_user_func($this->classComponent.'::getActionRoutesToRegister') as $route) { 79 | RouteFacade::{$route['method']}( 80 | $this->path . '/' . $route['path'], 81 | [$route['target_class'], $route['target_function']], 82 | )->name($this->baseName . '.' . $route['name']); 83 | } 84 | 85 | } 86 | 87 | public function __call(string $method, array $parameters): self 88 | { 89 | $this->proxiedMethodCalls[$method] = $parameters; 90 | 91 | return $this; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/Feature/InertiaTest.php: -------------------------------------------------------------------------------- 1 | markTestIncomplete("assertInertia not working for some reason"); 7 | $response = $this->get('/basic'); 8 | $response->assertInertia(function(Assert $page) { 9 | $page->has('one', '1') 10 | ->has('two', '2') 11 | ->has('three', '3') 12 | ->has('four', '4'); 13 | }); 14 | }); 15 | 16 | test('that the Basic component route works', function() { 17 | $this->get('/basic')->assertOk(); 18 | }); 19 | 20 | test('that the ParamComponent route works', function() { 21 | $this->get('/param-component/danmatthews')->assertOk(); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/Feature/NonHttpTests.php: -------------------------------------------------------------------------------- 1 | show() 10 | ) 11 | ->toBeArray() 12 | ->toHaveKeys(['three', 'four']) 13 | ->and( 14 | Basic::getActionRoutesToRegister() 15 | )->toBeEmpty(); 16 | }); 17 | 18 | test("Ensure the routes have been registered for the 'Basic' component.", function() { 19 | 20 | $routes = collect(Route::getRoutes()->getRoutesByName())->keys() 21 | ->filter(fn ($r) => str_starts_with($r, 'basic.')); 22 | 23 | expect($routes->count())->toBe(4) 24 | ->and($routes->contains('basic.show'))->toBeTrue() 25 | ->and($routes->contains('basic.store'))->toBeTrue() 26 | ->and($routes->contains('basic.update'))->toBeTrue() 27 | ->and($routes->contains('basic.destroy'))->toBeTrue(); 28 | }); 29 | 30 | test("Ensure the routes have been registered for the 'ActionRoutes' component.", function() { 31 | 32 | $routes = collect(Route::getRoutes()->getRoutesByName())->keys() 33 | ->filter(fn ($r) => str_starts_with($r, 'action-routes.')); 34 | 35 | expect($routes->count())->toBe(9) 36 | ->and($routes->contains('action-routes.get-action'))->toBeTrue() 37 | ->and($routes->contains('action-routes.post-action'))->toBeTrue() 38 | ->and($routes->contains('action-routes.put-action'))->toBeTrue() 39 | ->and($routes->contains('action-routes.patch-action'))->toBeTrue() 40 | ->and($routes->contains('action-routes.delete-action'))->toBeTrue(); 41 | }); 42 | 43 | test('The "ActionRoutes" component\'s getActionRoutesToRegister static getActionRoutesToRegister to ensure it returns the methods defined with the attributes.', function() { 44 | $actions = ActionRoutes::getActionRoutesToRegister(); 45 | expect($actions)->toHaveCount(5) 46 | ->and(collect($actions)->where('target_function', 'getAction'))->toHaveCount(1) 47 | ->and(collect($actions)->where('target_function', 'postAction'))->toHaveCount(1) 48 | ->and(collect($actions)->where('target_function', 'putAction'))->toHaveCount(1) 49 | ->and(collect($actions)->where('target_function', 'patchAction'))->toHaveCount(1) 50 | ->and(collect($actions)->where('target_function', 'deleteAction'))->toHaveCount(1); 51 | }); 52 | -------------------------------------------------------------------------------- /tests/Http/Inertia/ActionRoutes.php: -------------------------------------------------------------------------------- 1 | '3', 29 | 'four' => '4', 30 | ]; 31 | } 32 | 33 | 34 | #[GetAction] 35 | public function getAction() 36 | { 37 | 38 | } 39 | 40 | #[PostAction] 41 | public function postAction() 42 | { 43 | 44 | } 45 | #[PutAction] 46 | public function putAction() 47 | { 48 | 49 | } 50 | 51 | #[PatchAction] 52 | public function patchAction() 53 | { 54 | 55 | } 56 | 57 | #[DeleteAction] 58 | public function deleteAction() 59 | { 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /tests/Http/Inertia/Basic.php: -------------------------------------------------------------------------------- 1 | '3', 25 | 'four' => '4', 26 | ]; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/Http/Inertia/ParamComponent.php: -------------------------------------------------------------------------------- 1 | $username, 22 | ]; 23 | } 24 | } -------------------------------------------------------------------------------- /tests/InertiaComponentsServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Expectations 21 | |-------------------------------------------------------------------------- 22 | | 23 | | When you're writing tests, you often need to check that values meet certain conditions. The 24 | | "expect()" function gives you access to a set of "expectations" methods that you can use 25 | | to assert different things. Of course, you may extend the Expectation API at any time. 26 | | 27 | */ 28 | 29 | 30 | 31 | expect()->extend('toBeOne', function () { 32 | return $this->toBe(1); 33 | }); 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Functions 38 | |-------------------------------------------------------------------------- 39 | | 40 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 41 | | project that you don't want to repeat in every file. Here you can also expose helpers as 42 | | global functions to help you to reduce the number of lines of code in your test files. 43 | | 44 | */ 45 | -------------------------------------------------------------------------------- /tests/Providers/TestApplicationServiceProvider.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{-- @vite(['resources/js/app.js', 'resources/css/app.css'])--}} 7 | @inertiaHead 8 | 9 | 10 | @inertia 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | set('view.paths', [__DIR__.'/TestApp/resources/views']); 18 | } 19 | 20 | /** 21 | * Get package providers. 22 | * 23 | * @param \Illuminate\Foundation\Application $app 24 | * @return array> 25 | */ 26 | protected function getPackageProviders($app): array 27 | { 28 | return [ 29 | InertiaComponentsServiceProvider::class, 30 | TestApplicationServiceProvider::class, 31 | ]; 32 | } 33 | 34 | /** 35 | * Define routes setup. 36 | * 37 | * @param \Illuminate\Routing\Router $router 38 | * @return void 39 | */ 40 | protected function defineRoutes($router): void 41 | { 42 | $router->inertia('/basic', Basic::class)->name('basic'); 43 | $router->inertia('/action-routes', ActionRoutes::class)->name('action-routes'); 44 | $router->inertia('/param-component/{username}', ParamComponent::class)->name('param-component'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | toBeTrue(); 5 | }); 6 | --------------------------------------------------------------------------------