├── tests
├── test_app
│ ├── plugins
│ │ └── .gitkeep
│ ├── webroot
│ │ ├── js
│ │ │ └── app.js
│ │ └── mix-manifest.json
│ ├── templates
│ │ ├── Error
│ │ │ ├── error400.php
│ │ │ └── error500.php
│ │ └── layout
│ │ │ └── default.php
│ ├── config
│ │ ├── bootstrap.php
│ │ └── routes.php
│ └── src
│ │ ├── Controller
│ │ ├── AppController.php
│ │ ├── ErrorController.php
│ │ └── UsersController.php
│ │ └── Application.php
├── TestCase
│ ├── PluginTest.php
│ ├── View
│ │ ├── Helper
│ │ │ └── InertiaHelperTest.php
│ │ ├── InertiaJsonViewTest.php
│ │ └── InertiaWebViewTest.php
│ └── Controller
│ │ └── UsersControllerTest.php
└── bootstrap.php
├── templates
└── Inertia
│ └── app.php
├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── run-phpstan.yml
│ ├── check-coding-style.yml
│ └── run-tests.yml
├── CHANGELOG.md
├── phpstan.neon.dist
├── config
└── routes.php
├── phpcs.xml
├── .editorconfig
├── docs
├── README.md
├── ClientSideSetup.md
├── Installation.md
└── ServerSideSetup.md
├── src
├── Utility
│ └── Message.php
├── View
│ ├── InertiaJsonView.php
│ ├── InertiaWebView.php
│ ├── Helper
│ │ └── InertiaHelper.php
│ └── BaseViewTrait.php
├── InertiaPlugin.php
├── Middleware
│ └── InertiaMiddleware.php
└── Controller
│ └── InertiaResponseTrait.php
├── .gitignore
├── phpunit.xml.dist
├── LICENSE
├── composer.json
├── package.json
└── README.md
/tests/test_app/plugins/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/test_app/webroot/js/app.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/test_app/templates/Error/error400.php:
--------------------------------------------------------------------------------
1 | Test app template error 400
2 |
--------------------------------------------------------------------------------
/tests/test_app/templates/Error/error500.php:
--------------------------------------------------------------------------------
1 | Test app template error 500
2 |
--------------------------------------------------------------------------------
/templates/Inertia/app.php:
--------------------------------------------------------------------------------
1 | Inertia->make($page, 'app', '');
4 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | patreon: ishanvyas
4 |
--------------------------------------------------------------------------------
/tests/test_app/webroot/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/js/app.js": "/js/app.js?id=123456"
3 | }
4 |
--------------------------------------------------------------------------------
/tests/test_app/config/bootstrap.php:
--------------------------------------------------------------------------------
1 | plugin(
8 | 'Inertia',
9 | ['path' => '/inertia'],
10 | function (RouteBuilder $routes) {
11 | $routes->fallbacks(DashedRoute::class);
12 | }
13 | );
14 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 0
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/test_app/templates/layout/default.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | = $this->Html->charset() ?>
5 | assertStringContainsString('page="{"component":"Users', $result);
56 | $this->assertStringContainsString('class="container">
', $result);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.github/workflows/run-phpstan.yml:
--------------------------------------------------------------------------------
1 | name: Run PHPStan
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'cake5'
7 | - 'cake4'
8 | - 'cake3'
9 | pull_request:
10 | branches:
11 | - '*'
12 |
13 | jobs:
14 | run-phpstan:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | matrix:
20 | php-versions: ['8.1']
21 |
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v2
25 |
26 | - name: Validate composer.json and composer.lock
27 | run: composer validate
28 |
29 | - name: Get composer cache directory
30 | id: composer-cache
31 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
32 |
33 | - name: Get date part for cache key
34 | id: key-date
35 | run: echo "::set-output name=date::$(date +'%Y-%m')"
36 |
37 | - name: Cache composer dependencies
38 | uses: actions/cache@v2
39 | with:
40 | path: ${{ steps.composer-cache.outputs.dir }}
41 | key: ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
42 | restore-keys: |
43 | ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
44 |
45 | - name: Setup PHP
46 | uses: shivammathur/setup-php@v2
47 | with:
48 | php-version: ${{ matrix.php-versions }}
49 | extensions: mbstring, intl
50 |
51 | - name: Install dependencies
52 | if: steps.composer-cache.outputs.cache-hit != 'true'
53 | run: composer install --no-interaction
54 |
55 | - name: Run PHPStan command
56 | run: composer analyse
57 |
--------------------------------------------------------------------------------
/.github/workflows/check-coding-style.yml:
--------------------------------------------------------------------------------
1 | name: Check Coding Style
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'cake5'
7 | - 'cake4'
8 | - 'cake3'
9 | pull_request:
10 | branches:
11 | - '*'
12 |
13 | jobs:
14 | check-coding-style:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | matrix:
20 | php-versions: ['8.1']
21 |
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v2
25 |
26 | - name: Validate composer.json and composer.lock
27 | run: composer validate
28 |
29 | - name: Get composer cache directory
30 | id: composer-cache
31 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
32 |
33 | - name: Get date part for cache key
34 | id: key-date
35 | run: echo "::set-output name=date::$(date +'%Y-%m')"
36 |
37 | - name: Cache composer dependencies
38 | uses: actions/cache@v2
39 | with:
40 | path: ${{ steps.composer-cache.outputs.dir }}
41 | key: ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
42 | restore-keys: |
43 | ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}
44 |
45 | - name: Setup PHP
46 | uses: shivammathur/setup-php@v2
47 | with:
48 | php-version: ${{ matrix.php-versions }}
49 | extensions: mbstring, intl
50 |
51 | - name: Install dependencies
52 | if: steps.composer-cache.outputs.cache-hit != 'true'
53 | run: composer install --no-interaction
54 |
55 | - name: Run cs check command
56 | run: composer cs-check
57 |
--------------------------------------------------------------------------------
/tests/test_app/src/Controller/UsersController.php:
--------------------------------------------------------------------------------
1 | 1,
15 | 'name' => 'John Doe',
16 | ],
17 | [
18 | 'id' => 2,
19 | 'name' => 'Alie Doe',
20 | ],
21 | ];
22 |
23 | $posts = [
24 | [
25 | 'title' => 'Title 1',
26 | 'body' => 'Body of title 1',
27 | ],
28 | [
29 | 'title' => 'Title 2',
30 | 'body' => 'Body of title 2',
31 | ],
32 | ];
33 |
34 | $postsCount = count($posts);
35 |
36 | $this->set(compact('users', 'posts', 'postsCount'));
37 | }
38 |
39 | public function customComponent()
40 | {
41 | $component = 'Custom/Component';
42 | $users = [
43 | [
44 | 'id' => 1,
45 | 'name' => 'John Doe',
46 | ],
47 | [
48 | 'id' => 2,
49 | 'name' => 'Alie Doe',
50 | ],
51 | ];
52 |
53 | $this->set(compact('users', 'component'));
54 | }
55 |
56 | public function store()
57 | {
58 | return $this->redirect('/users/index');
59 | }
60 |
61 | public function internalServerError()
62 | {
63 | throw new Exception();
64 | }
65 |
66 | public function setSuccessFlash()
67 | {
68 | $this->Flash->success('User saved successfully.');
69 | }
70 |
71 | public function setErrorFlash()
72 | {
73 | $this->Flash->error('Something went wrong!');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/.github/workflows/run-tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'cake5'
7 | - 'cake4'
8 | - 'cake3'
9 | pull_request:
10 | branches:
11 | - '*'
12 |
13 | jobs:
14 | run-tests:
15 |
16 | runs-on: ubuntu-latest
17 |
18 | strategy:
19 | matrix:
20 | php-versions: ['8.1', '8.2', '8.3']
21 | composer-type: [lowest, stable, dev]
22 |
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v2
26 |
27 | - name: Validate composer.json and composer.lock
28 | run: composer validate
29 |
30 | - name: Get composer cache directory
31 | id: composer-cache
32 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
33 |
34 | - name: Get date part for cache key
35 | id: key-date
36 | run: echo "::set-output name=date::$(date +'%Y-%m')"
37 |
38 | - name: Cache composer dependencies
39 | uses: actions/cache@v2
40 | with:
41 | path: ${{ steps.composer-cache.outputs.dir }}
42 | key: ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.composer-type }}
43 | restore-keys: |
44 | ${{ runner.os }}-${{ github.ref }}-composer-${{ steps.key-date.outputs.date }}-${{ hashFiles('composer.json') }}-${{ matrix.composer-type }}
45 |
46 | - name: Setup PHP
47 | uses: shivammathur/setup-php@v2
48 | with:
49 | php-version: ${{ matrix.php-versions }}
50 | extensions: mbstring, intl
51 |
52 | - name: Install dependencies
53 | if: steps.composer-cache.outputs.cache-hit != 'true'
54 | run: |
55 | if [[ ${{ matrix.composer-type }} == 'lowest' ]]; then
56 | composer update --prefer-dist --no-progress --no-interaction --prefer-stable --prefer-lowest
57 | elif [[ ${{ matrix.composer-type }} == 'stable' ]]; then
58 | composer update --prefer-dist --no-progress --no-interaction --prefer-stable
59 | else
60 | composer update --prefer-dist --no-progress --no-interaction
61 | fi
62 |
63 | - name: Run tests
64 | run: composer test
65 |
--------------------------------------------------------------------------------
/docs/ClientSideSetup.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ## Client-side Setup
4 |
5 | Once you have your [server-side framework configured](ServerSideSetup.md), you then need to [setup your client-side framework](https://inertiajs.com/client-side-setup). This plugin leverages [AssetMix plugin](https://github.com/ishanvyas22/asset-mix/tree/master) so you don't have to install all the front-end dependencies one by one. Instead you can generate scaffolding(using above command) to quickly get started.
6 |
7 | **Note:** As of now AssetMix plugin only supports front-end scaffolding for Vue.js. If you want to use any other client side framework you can do so, refer [this](https://inertiajs.com/client-side-setup) link to know more.
8 |
9 | ### Adding new pages
10 |
11 | As described in official documentation,
12 |
13 | > With Inertia, each page in your application has its own controller and corresponding JavaScript component. This allows you to retrieve just the data necessary for that page, no API required.
14 |
15 | So to create new page you need to create a JavaScript component into `assets/js/` folder matching CakePHP convention. For example, the `index` method of a `ArticlesController` would use `assets/js/Pages/Articles/Index.vue` as its view component.
16 |
17 | Refer official documentation for more info: https://inertiajs.com/pages#creating-pages
18 |
19 | ### Resolve component from another path
20 |
21 | By default, all your components should go into the `Pages` directory inside your js root path(`assets/js` or `resources/js`) if you want to change this path, you have to edit `app.js` file to tell bundler to resolve another path.
22 |
23 | ```js
24 | import Vue from 'vue';
25 | import { InertiaApp } from '@inertiajs/inertia-vue';
26 |
27 | Vue.use(InertiaApp);
28 |
29 | const el = document.getElementById('app');
30 |
31 | new Vue({
32 | render: h => h(InertiaApp, {
33 | props: {
34 | initialPage: JSON.parse(el.dataset.page),
35 | resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default),
36 | },
37 | }),
38 | }).$mount(el);
39 | ```
40 |
41 | Refer official documentation for more info: https://inertiajs.com/client-side-setup#initialize-app
42 |
43 | ---
44 |
45 | [< Server-side Setup](ServerSideSetup.md)
46 |
--------------------------------------------------------------------------------
/docs/Installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | 1. Add this plugin to your application with [composer](https://getcomposer.org)
4 |
5 | ```bash
6 | composer require ishanvyas22/cakephp-inertiajs
7 | ```
8 |
9 | 2. Load plugin into your application
10 |
11 | ```bash
12 | bin/cake plugin load Inertia
13 | ```
14 |
15 | 3. Generate Client-side scaffolding (Inertia.js + Vue.js)
16 |
17 | ```bash
18 | # Generate vue scaffolding. You can also use (react) here.
19 | bin/cake asset_mix generate inertia-vue
20 |
21 | # Install javascript tooling and dependencies.
22 | npm install
23 | ```
24 |
25 | **Note:** In order to use Inertia.js, you will need one of [Client-side adapters](ClientSideSetup.md).
26 |
27 | 4. Just add below line into your layout(`Template/Layout/default.ctp`) file
28 |
29 | ```php
30 | echo $this->AssetMix->script('app');
31 | ```
32 |
33 | Your layout file should look something like this:
34 |
35 | ```php
36 |
37 |
38 |
39 | = $this->Html->charset() ?>
40 | assertStringContainsString(htmlentities('"props":{"user":{"id":1,"name":"John Doe"}}'), $result);
49 | }
50 |
51 | public function testRendersComponentName()
52 | {
53 | $this->View->set('component', 'Users/Index');
54 | $this->View->set('user', ['id' => 1, 'name' => 'John Doe']);
55 |
56 | $result = $this->View->render();
57 |
58 | $this->assertStringContainsString('"Users\/Index"', $result);
59 | }
60 |
61 | public function testItCanPassthruVarsToCake()
62 | {
63 | $this->View->set('component', 'Users/Index');
64 | $this->View->set('user', ['id' => 1, 'name' => 'John Doe']);
65 | $this->View->set('one', 1);
66 | $this->View->set('two', 2);
67 | $this->View->set('three', 3);
68 |
69 | $actual = $this->View->getVars();
70 |
71 | $expected = [
72 | 'three',
73 | 'two',
74 | 'one',
75 | 'user',
76 | 'component',
77 | ];
78 |
79 | $this->assertEquals($expected, $actual);
80 |
81 | $this->View->render();
82 | $actual = $this->View->getVars();
83 |
84 | $expected = [
85 | 'page',
86 | 'three',
87 | 'two',
88 | 'one',
89 | ];
90 |
91 | $this->assertEquals($expected, $actual);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Controller/InertiaResponseTrait.php:
--------------------------------------------------------------------------------
1 | isErrorStatus() || $this->isFailureStatus()) {
17 | return null;
18 | }
19 |
20 | $this->setViewBuilderClass();
21 |
22 | $this->setFlashData();
23 |
24 | $this->setCsrfToken();
25 |
26 | $this->setNonInertiaProps();
27 | }
28 |
29 | /**
30 | * Sets array of view variables for Inertia to ignore for use
31 | * by Non-Inertia view elements
32 | *
33 | * @return void
34 | */
35 | private function setNonInertiaProps(): void
36 | {
37 | if (!property_exists($this, '_nonInertiaProps')) {
38 | return;
39 | }
40 |
41 | $nonInertiaProps = $this->_nonInertiaProps;
42 |
43 | if (is_string($nonInertiaProps)) {
44 | $nonInertiaProps = [$nonInertiaProps];
45 | }
46 |
47 | $this->viewbuilder()
48 | ->setOption('_nonInertiaProps', $nonInertiaProps);
49 | }
50 |
51 | /**
52 | * Sets view class depending on detector.
53 | *
54 | * @return void
55 | */
56 | private function setViewBuilderClass(): void
57 | {
58 | $viewClass = 'InertiaWeb';
59 | if ($this->getRequest()->is('inertia')) {
60 | $viewClass = 'InertiaJson';
61 | }
62 |
63 | $this->viewBuilder()->setClassName("Inertia.{$viewClass}");
64 | }
65 |
66 | /**
67 | * Checks if response status code is 404.
68 | *
69 | * @return bool Returns true if response is error, false otherwise.
70 | */
71 | private function isErrorStatus(): bool
72 | {
73 | $statusCode = $this->getResponse()->getStatusCode();
74 | $errorCodes = [
75 | Message::STATUS_NOT_FOUND,
76 | ];
77 |
78 | if (in_array($statusCode, $errorCodes)) {
79 | return true;
80 | }
81 |
82 | return false;
83 | }
84 |
85 | /**
86 | * Checks if response status code is 500.
87 | *
88 | * @return bool Returns true if response is failure, false otherwise.
89 | */
90 | private function isFailureStatus(): bool
91 | {
92 | $statusCode = $this->getResponse()->getStatusCode();
93 | $failureCodes = [
94 | Message::STATUS_INTERNAL_SERVER,
95 | ];
96 |
97 | if (in_array($statusCode, $failureCodes)) {
98 | return true;
99 | }
100 |
101 | return false;
102 | }
103 |
104 | /**
105 | * Sets flash data, and deletes after setting it.
106 | *
107 | * @return void
108 | */
109 | protected function setFlashData(): void
110 | {
111 | /** @var \Cake\Http\Session $session */
112 | $session = $this->getRequest()->getSession();
113 |
114 | $this->set('flash', function () use ($session) {
115 | if (!$session->check('Flash.flash.0')) {
116 | return [];
117 | }
118 |
119 | $flash = $session->read('Flash.flash.0');
120 |
121 | $flash['element'] = strtolower(str_replace('/', '-', $flash['element']));
122 |
123 | $session->delete('Flash');
124 |
125 | return $flash;
126 | });
127 | }
128 |
129 | /**
130 | * Sets `_csrfToken` field to pass into every front-end component.
131 | *
132 | * @return void
133 | */
134 | private function setCsrfToken(): void
135 | {
136 | $this->set('_csrfToken', $this->getRequest()->getAttribute('csrfToken'));
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/View/BaseViewTrait.php:
--------------------------------------------------------------------------------
1 | getRequest()->getRequestTarget(), true);
20 | }
21 |
22 | /**
23 | * Returns component name.
24 | * If passed via contoller using `component` key, will use that.
25 | * Otherwise, will return the combination of controller and action.
26 | *
27 | * That means, it will generate `Users/Index` component for `UsersController.php`'s `index` action.
28 | *
29 | * @return string
30 | */
31 | private function getComponentName(): string
32 | {
33 | if ($this->get('component') !== null) {
34 | $component = $this->get('component');
35 |
36 | unset($this->viewVars['component']);
37 |
38 | return $this->convertToString($component);
39 | }
40 |
41 | return sprintf(
42 | '%s/%s',
43 | $this->convertToString($this->getRequest()->getParam('controller')),
44 | ucwords($this->convertToString($this->getRequest()->getParam('action'))),
45 | );
46 | }
47 |
48 | /**
49 | * Helper method used to ensure correct string type.
50 | *
51 | * @param mixed $value Value
52 | * @return string
53 | * @throws \UnexpectedValueException
54 | */
55 | protected function convertToString(mixed $value): string
56 | {
57 | if (is_string($value)) {
58 | return $value;
59 | }
60 | if (is_numeric($value)) {
61 | return (string)$value;
62 | }
63 |
64 | throw new UnexpectedValueException();
65 | }
66 |
67 | /**
68 | * Returns an array of Inertia var names and sets Non Inertia Vars for use by View
69 | *
70 | * @param array $only Partial Data
71 | * @param array $passedViewVars Associative array of all passed ViewVars and their values
72 | * @return array
73 | */
74 | private function filterViewVars(array $only, array $passedViewVars): array
75 | {
76 | $onlyViewVars = !empty($only) ? $only : array_keys($passedViewVars);
77 |
78 | /** @var array
$nonInertiaProps */
79 | $nonInertiaProps = $this->getConfig('_nonInertiaProps') ?? [];
80 |
81 | /**
82 | * Selects the Non-Inertia Vars to be available
83 | * for use outside of Inertia Components
84 | */
85 | $this->viewVars = array_intersect_key(
86 | $passedViewVars,
87 | array_flip($nonInertiaProps)
88 | );
89 |
90 | /**
91 | * Returns an array of the vars names which will be
92 | * packaged into the Inertia `page` view var
93 | */
94 |
95 | return array_diff($onlyViewVars, $nonInertiaProps);
96 | }
97 |
98 | /**
99 | * Returns `props` array excluding the default variables.
100 | *
101 | * @return array
102 | */
103 | private function getProps(): array
104 | {
105 | $props = [];
106 |
107 | $only = $this->getPartialData();
108 |
109 | $passedViewVars = $this->viewVars;
110 |
111 | $onlyViewVars = $this->filterViewVars($only, $passedViewVars);
112 |
113 | foreach ($onlyViewVars as $varName) {
114 | if (!isset($passedViewVars[$varName])) {
115 | continue;
116 | }
117 |
118 | $prop = $passedViewVars[$varName];
119 |
120 | if ($prop instanceof Closure) {
121 | $props[$varName] = $prop();
122 | } else {
123 | $props[$varName] = $prop;
124 | }
125 | }
126 |
127 | return $props;
128 | }
129 |
130 | /**
131 | * Returns view variable names from `X-Inertia-Partial-Data` header.
132 | *
133 | * @return array
134 | */
135 | public function getPartialData(): array
136 | {
137 | if (!$this->getRequest()->is('inertia-partial-data')) {
138 | return [];
139 | }
140 |
141 | return explode(
142 | ',',
143 | $this->getRequest()->getHeader('X-Inertia-Partial-Data')[0]
144 | );
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/TestCase/Controller/UsersControllerTest.php:
--------------------------------------------------------------------------------
1 | get('/users/index');
19 |
20 | $this->assertResponseOk();
21 | $this->assertContentType('text/html');
22 | $this->assertTemplate('app');
23 | $this->assertResponseContains('"Users\/Index"');
24 | $this->assertResponseContains('"http:\/\/localhost\/users\/index"');
25 | $this->assertResponseContains('"name":"InertiaTestApp"');
26 | $this->assertResponseContains('props');
27 | }
28 |
29 | public function testItReturnsInertiaWebViewResponseWithCustomComponent()
30 | {
31 | $this->get('/users/custom-component');
32 |
33 | $this->assertResponseOk();
34 | $this->assertContentType('text/html');
35 | $this->assertTemplate('app');
36 | $this->assertResponseContains(htmlentities(json_encode('Custom/Component')));
37 | }
38 |
39 | public function testItReturnsInertiaJsonViewResponseWhenRequestIsXInertia()
40 | {
41 | $this->configRequest([
42 | 'headers' => ['X-Inertia' => 'true'],
43 | ]);
44 |
45 | $this->get('/users/index');
46 |
47 | $this->assertResponseOk();
48 | $this->assertContentType('application/json');
49 | }
50 |
51 | public function testPartiaReloads()
52 | {
53 | $this->configRequest([
54 | 'headers' => [
55 | 'X-Inertia' => 'true',
56 | 'X-Inertia-Partial-Data' => 'posts,postsCount',
57 | 'X-Inertia-Partial-Component' => 'Users/Index',
58 | ],
59 | ]);
60 |
61 | $this->get('/users/index');
62 |
63 | $this->assertContentType('application/json');
64 | $this->assertResponseOk();
65 |
66 | $expected = json_encode([
67 | 'component' => 'Users/Index',
68 | 'url' => 'http://localhost/users/index',
69 | 'props' => [
70 | 'posts' => [
71 | [
72 | 'title' => 'Title 1',
73 | 'body' => 'Body of title 1',
74 | ],
75 | [
76 | 'title' => 'Title 2',
77 | 'body' => 'Body of title 2',
78 | ],
79 | ],
80 | 'postsCount' => 2,
81 | ],
82 | ], JSON_PRETTY_PRINT);
83 | $this->assertEquals($expected, $this->_getBodyAsString());
84 | }
85 |
86 | public function testItRedirectsWithSeeOtherResponseCode()
87 | {
88 | $this->enableCsrfToken();
89 | $this->configRequest([
90 | 'headers' => ['X-Inertia' => 'true'],
91 | ]);
92 |
93 | $this->put('/users/store', ['test' => 'data']);
94 |
95 | $this->assertResponseCode(Message::STATUS_SEE_OTHER);
96 | }
97 |
98 | public function testItReturnsHtmlResultFor404()
99 | {
100 | $this->configRequest([
101 | 'headers' => [
102 | 'X-Inertia' => 'true',
103 | 'X-Requested-With' => 'XMLHttpRequest',
104 | ],
105 | ]);
106 |
107 | $this->get('/404');
108 |
109 | $this->assertResponseCode(404);
110 | $this->assertContentType('text/html');
111 | }
112 |
113 | public function testItReturnsHtmlResultFor500()
114 | {
115 | $this->configRequest([
116 | 'headers' => [
117 | 'X-Inertia' => 'true',
118 | 'X-Requested-With' => 'XMLHttpRequest',
119 | ],
120 | ]);
121 |
122 | $this->get('/users/internal-server-error');
123 |
124 | $this->assertResponseCode(500);
125 | $this->assertContentType('text/html');
126 | }
127 |
128 | public function testItSetsSuccessFlashDataIntoProps()
129 | {
130 | $this->configRequest([
131 | 'headers' => ['X-Inertia' => 'true'],
132 | ]);
133 |
134 | $this->get('/users/set-success-flash');
135 | $responseArray = json_decode($this->_getBodyAsString(), true);
136 |
137 | $this->assertResponseOk();
138 | $this->assertArrayHasKey('flash', $responseArray['props']);
139 | $this->assertEquals([
140 | 'message' => 'User saved successfully.',
141 | 'key' => 'flash',
142 | 'element' => 'flash-success',
143 | 'params' => [],
144 | ], $responseArray['props']['flash']);
145 | }
146 |
147 | public function testItSetsErrorFlashDataIntoProps()
148 | {
149 | $this->configRequest([
150 | 'headers' => ['X-Inertia' => 'true'],
151 | ]);
152 |
153 | $this->get('/users/set-error-flash');
154 | $responseArray = json_decode($this->_getBodyAsString(), true);
155 |
156 | $this->assertResponseOk();
157 | $this->assertArrayHasKey('flash', $responseArray['props']);
158 | $this->assertEquals([
159 | 'message' => 'Something went wrong!',
160 | 'key' => 'flash',
161 | 'element' => 'flash-error',
162 | 'params' => [],
163 | ], $responseArray['props']['flash']);
164 | }
165 |
166 | public function testPropsContainsCsrfToken()
167 | {
168 | $this->configRequest([
169 | 'headers' => ['X-Inertia' => 'true'],
170 | ]);
171 |
172 | $this->get('/users/index');
173 | $responseArray = json_decode($this->_getBodyAsString(), true);
174 |
175 | $this->assertResponseOk();
176 | $this->assertArrayHasKey('_csrfToken', $responseArray['props']);
177 | $this->assertNotEmpty($responseArray['props']['_csrfToken']);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/docs/ServerSideSetup.md:
--------------------------------------------------------------------------------
1 | # Usage
2 |
3 | ## Server-side Setup
4 |
5 | To start using Inertia.js into your app, you just have to add `Inertia\Controller\InertiaResponseTrait` into your `AppController.php` controller. And it will automatically render the response according to the request.
6 |
7 | ```php
8 | ` to boot your JavaScript application in. For more info visit [this link](https://inertiajs.com/server-side-setup#root-template).
28 |
29 | By default, it will use the plugin's `Template/Inertia/app.php` file as root template.
30 |
31 | If you want to override this, read [Overriding Plugin Templates from Inside Your Application](https://book.cakephp.org/4/en/plugins.html#overriding-plugin-templates-from-inside-your-application) section from cookbook. After creating the template file, you can use [`InertiaHelper`](https://github.com/ishanvyas22/cakephp-inertiajs/blob/master/src/View/Helper/InertiaHelper.php#L20) to quickly generate a `` element.
32 |
33 | ```php
34 | Inertia->make($page, 'app', 'container clearfix');
37 | ```
38 |
39 | **Output:**
40 |
41 | ```html
42 |
43 | ```
44 |
45 | ### Render different components
46 |
47 | This plugin follows same convention as CakePHP, so for `UsersController.php`'s `index` action it will render `Index.vue` component inside `Users` directory by default.
48 |
49 | In case, you want to render any other component just set `$component` view variable and it will render that component accordingly. For example, if you want to render `Listing.vue` file inside of `Users` directory:
50 |
51 | ```php
52 | set('users', $this->Users->find()->toArray());
63 | $this->set('component', 'Users/Listing');
64 | }
65 | }
66 | ```
67 |
68 | ### Customize `beforeRender` hook
69 |
70 | `InertiaResponseTrait` uses `beforeRender` hook internally to handle the view specific logic. But there might be some scenarios in which you want to use this hook to manipulate or customize some behavior. Since if you will directly call `beforeRender` method in your controller, it will override whole behavior which is not what you want.
71 |
72 | So instead you should do like this:
73 |
74 | ```php
75 | inertiaBeforeRender($event);
93 |
94 | // Do your customization here.
95 | }
96 | }
97 | ```
98 |
99 | **Note:** You must have to call `beforeRender` method of `InertiaResponseTrait`, otherwise the inertia response won't work as expected.
100 |
101 | ### Flash messages
102 |
103 | When working with CakePHP we often use Flash messages to set one-time notification message to acknowledge user that particular action has succeeded or not. This plugin makes it easier to work with Flash messages, because you don't have to do anything extra make flash messages work.
104 |
105 | To pass flash data into the front-end component, you simply have to set flash message as you normally would:
106 |
107 | ```php
108 | $this->Flash->success('It worked!');
109 | ```
110 | That's it! The flash data will automatically pass to component as a `Array` for you to use.
111 |
112 | ### Integrating Inertia with CakePHP layout and template elements
113 |
114 | To include a mix of legacy CakePHP elements (such as a menu system) with Inertia components tell Inertia which view variables NOT to include in the root Inertia `div` using the `$_nonInteriaProps` class property
115 |
116 | In `AppController` (to globally set the Non-Interia vars) or a child that inherits from it (to make the change per Controller) set the `$_nonInertiaProps` class property to an array of View Vars:
117 |
118 |
119 | ```php
120 | _nonInertiaProps = Configure::read('NON_INERTIA_PROPS')
143 | // }
144 |
145 | // See `Customize beforeRender hook` regarding further customization
146 |
147 | public function index() {
148 | // menu array
149 | $menu = $this->Examples->Menus->find('threaded');
150 |
151 | // user info to display logged in user in menu
152 | $user = $this->Authentication->getIndentity();
153 |
154 | $examples = $this->Examples->find('all);
155 |
156 | // `menu` and `user` will be available to CakePHP view elements
157 | // `examples` will be available to Inertia Components
158 | $this->set(compact('menu', 'user', 'examples'));
159 | }
160 | }
161 | ```
162 |
163 | ---
164 |
165 | [< Installation](Installation.md) | [Client-side Setup >](ClientSideSetup.md)
166 |
--------------------------------------------------------------------------------