├── .github
└── workflows
│ └── .ci.yml
├── .gitignore
├── CHANGELOG.md
├── CONFIGURATION.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── USAGE.md
├── index.d.ts
├── index.js
├── loader.php
├── package-lock.json
├── package.json
├── renovate.json
└── test
├── lib
├── _expressSetup.js
└── templates
│ ├── basicTest.php
│ ├── codingError.php
│ ├── registerGlobalModelTest.php
│ └── selfContainedTest.php
└── test.js
/.github/workflows/.ci.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: CI
3 | jobs:
4 | test:
5 | runs-on: ${{ matrix.os }}
6 | strategy:
7 | matrix:
8 | node-version: [20.x, 22.x, 24.x]
9 | php-versions: ['8.1', '8.2', '8.3', '8.4']
10 | os: [ubuntu-latest, macos-latest, windows-latest]
11 | steps:
12 | - uses: actions/checkout@v4
13 | - name: Use Node.js ${{ matrix.node-version }}
14 | uses: actions/setup-node@v4
15 | with:
16 | node-version: ${{ matrix.node-version }}
17 | - name: Setup PHP Action
18 | uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: ${{ matrix.php-versions }}
21 | ini-values: short_open_tag=On, error_reporting=E_ALL & ~E_NOTICE
22 | - run: npm ci
23 | - run: npm run lint
24 | - run: npm run coverage
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Linux ###
2 | *~
3 |
4 | # temporary files which can be created if a process still has a handle open of a deleted file
5 | .fuse_hidden*
6 |
7 | # KDE directory preferences
8 | .directory
9 |
10 | # Linux trash folder which might appear on any partition or disk
11 | .Trash-*
12 |
13 | # .nfs files are created when an open file is removed but is still being accessed
14 | .nfs*
15 |
16 | ### macOS ###
17 | # General
18 | .DS_Store
19 | .AppleDouble
20 | .LSOverride
21 |
22 | # Icon must end with two \r
23 | Icon
24 |
25 | # Thumbnails
26 | ._*
27 |
28 | # Files that might appear in the root of a volume
29 | .DocumentRevisions-V100
30 | .fseventsd
31 | .Spotlight-V100
32 | .TemporaryItems
33 | .Trashes
34 | .VolumeIcon.icns
35 | .com.apple.timemachine.donotpresent
36 |
37 | # Directories potentially created on remote AFP share
38 | .AppleDB
39 | .AppleDesktop
40 | Network Trash Folder
41 | Temporary Items
42 | .apdisk
43 |
44 | ### Node ###
45 | # Logs
46 | logs
47 | *.log
48 | npm-debug.log*
49 | yarn-debug.log*
50 | yarn-error.log*
51 | lerna-debug.log*
52 |
53 | # Diagnostic reports (https://nodejs.org/api/report.html)
54 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
55 |
56 | # Runtime data
57 | pids
58 | *.pid
59 | *.seed
60 | *.pid.lock
61 |
62 | # Directory for instrumented libs generated by jscoverage/JSCover
63 | lib-cov
64 |
65 | # Coverage directory used by tools like istanbul
66 | coverage
67 | *.lcov
68 |
69 | # nyc test coverage
70 | .nyc_output
71 |
72 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
73 | .grunt
74 |
75 | # Bower dependency directory (https://bower.io/)
76 | bower_components
77 |
78 | # node-waf configuration
79 | .lock-wscript
80 |
81 | # Compiled binary addons (https://nodejs.org/api/addons.html)
82 | build/Release
83 |
84 | # Dependency directories
85 | node_modules/
86 | jspm_packages/
87 |
88 | # TypeScript v1 declaration files
89 | typings/
90 |
91 | # TypeScript cache
92 | *.tsbuildinfo
93 |
94 | # Optional npm cache directory
95 | .npm
96 |
97 | # Optional eslint cache
98 | .eslintcache
99 |
100 | # Optional REPL history
101 | .node_repl_history
102 |
103 | # Output of 'npm pack'
104 | *.tgz
105 |
106 | # Yarn Integrity file
107 | .yarn-integrity
108 |
109 | # dotenv environment variables file
110 | .env
111 | .env.test
112 |
113 | # parcel-bundler cache (https://parceljs.org/)
114 | .cache
115 |
116 | # next.js build output
117 | .next
118 |
119 | # nuxt.js build output
120 | .nuxt
121 |
122 | # rollup.js default build output
123 | dist/
124 |
125 | # Uncomment the public line if your project uses Gatsby
126 | # https://nextjs.org/blog/next-9-1#public-directory-support
127 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav
128 | # public
129 |
130 | # Storybook build outputs
131 | .out
132 | .storybook-out
133 |
134 | # vuepress build output
135 | .vuepress/dist
136 |
137 | # Serverless directories
138 | .serverless/
139 |
140 | # FuseBox cache
141 | .fusebox/
142 |
143 | # DynamoDB Local files
144 | .dynamodb/
145 |
146 | # Temporary folders
147 | tmp/
148 | temp/
149 |
150 | ### Windows ###
151 | # Windows thumbnail cache files
152 | Thumbs.db
153 | Thumbs.db:encryptable
154 | ehthumbs.db
155 | ehthumbs_vista.db
156 |
157 | # Dump file
158 | *.stackdump
159 |
160 | # Folder config file
161 | [Dd]esktop.ini
162 |
163 | # Recycle Bin used on file shares
164 | $RECYCLE.BIN/
165 |
166 | # Windows Installer files
167 | *.cab
168 | *.msi
169 | *.msix
170 | *.msm
171 | *.msp
172 |
173 | # Windows shortcuts
174 | *.lnk
175 |
176 | .npm-cache
177 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.1.0
2 |
3 | - Added `run` and `runWithData` methods so this module can be used as a general purpose PHP runner.
4 | - Updated various dependencies.
5 |
6 | ## 1.0.2
7 |
8 | - Added TypeScript definitions.
9 | - Various dependencies bumped.
10 |
11 | ## 1.0.1
12 |
13 | - Renamed package from `express-php-view-engine` to `php`. Thanks to Elmer Bulthuis for transferring the package name.
14 | - Various dependencies bumped.
15 |
16 | ## 1.0.0
17 |
18 | - Initial version of superseding project.
19 | - `express-php-view-engine` notably does not attempt to finish the work of the old `php` module. Instead it allows the native PHP parser to execute as a child process within Express applications.
20 |
21 | ## 0.0.1
22 |
23 | - Initial version of original project.
24 | - Originally the `php` module on npm was used by a separate project that attempted to implement a PHP parser in JavaScript for Node.js, but was never finished. One work in progress version was published, then the project remained stale for 8 years.
25 |
--------------------------------------------------------------------------------
/CONFIGURATION.md:
--------------------------------------------------------------------------------
1 | This module will register values from the data model you pass to the PHP script as global variables in your PHP script by default when you use PHP as an Express view engine or when you call `runWithData`. You can disable this behavior if desired in the following ways:
2 |
3 | Disable registering globally:
4 |
5 | ```js
6 | const php = require('php')
7 | php.disableRegisterGlobalModel()
8 | // can be reenabled by calling php.enableRegisterGlobalModel()
9 | ```
10 |
11 | Disable registering on a per render basis in Express:
12 |
13 | ```js
14 | app.get('/', (req, res) => {
15 | res.render('index.php', {
16 | _REGISTER_GLOBAL_MODEL: false,
17 | hello: 'world'
18 | })
19 | })
20 | ```
21 |
22 | Disable registering on a per render basis in `runWithData` (though if you're doing this, you probably should just use `php.run()` instead, as that method was written to use simpler logic that doesn't support passing data to PHP):
23 |
24 | ```js
25 | const output = await php.runWithData('some_php_script.php', {
26 | _REGISTER_GLOBAL_MODEL: false,
27 | hello: 'world'
28 | })
29 | ```
30 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | ## Before opening a pull request
4 |
5 | - Be sure all tests pass: `npm t`.
6 | - Ensure 100% code coverage and write new tests if necessary: `npm run coverage`.
7 | - Add your changes to `CHANGELOG.md`.
8 |
9 | ## Release process
10 |
11 | If you are a maintainer of this module, please follow the following release procedure:
12 |
13 | - Merge all desired pull requests into master.
14 | - Bump `package.json` to a new version and run `npm i` to generate a new `package-lock.json`.
15 | - Alter CHANGELOG "Next version" section and stamp it with the new version.
16 | - Paste contents of CHANGELOG into new version commit.
17 | - Open and merge a pull request with those changes.
18 | - Tag the merge commit as the a new release version number.
19 | - Publish commit to npm.
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | License
2 | ===
3 |
4 | All original code in this project is licensed under the [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). Commercial and noncommercial use is permitted with attribution.
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 🐘 **php** [](https://www.npmjs.com/package/php)
2 |
3 | This module allows you to run [PHP](https://php.net) code in Node.js in various ways:
4 |
5 | - Run PHP scripts.
6 | - Run PHP scripts and pass them JSON data from Node.js.
7 | - Use PHP as a view engine (templating system) for [Express framework](https://expressjs.com) applications.
8 |
9 | This module was built and is maintained by the [Roosevelt web framework](https://rooseveltframework.org) [team](https://rooseveltframework.org/contributors), but it can be used independently of Roosevelt as well.
10 |
11 | Documentation
13 |
14 |
17 |
=$hello?>
21 | ``` 22 | 23 | The output will be: 24 | 25 | ```html 26 |world
27 | ``` 28 | 29 | ## Use with Express 30 | 31 | ```js 32 | const express = require('express') 33 | const app = express() 34 | const php = require('php') 35 | 36 | // setup PHP templating engine 37 | app.set('views', path.join(__dirname, 'templates')) 38 | app.set('view engine', 'php') // set PHP as a view engine in your Express app 39 | app.engine('php', php.__express) 40 | 41 | // define a route 42 | app.get('/', (req, res) => { 43 | res.render('index.php', { 44 | hello: 'world' 45 | }) 46 | }) 47 | ``` 48 | 49 | Then, assuming your `templates/index.php` looks like this: 50 | 51 | ```php 52 |=$hello?>
53 | ``` 54 | 55 | The output will be: 56 | 57 | ```html 58 |world
59 | ``` 60 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "php" { 2 | export async function __express(template: any, model: any, callback: any): Promise=$hello?>
2 | -------------------------------------------------------------------------------- /test/lib/templates/codingError.php: -------------------------------------------------------------------------------- 1 | 4 |=$hello?>
5 | -------------------------------------------------------------------------------- /test/lib/templates/registerGlobalModelTest.php: -------------------------------------------------------------------------------- 1 |= isset($hello) ? $hello : '' ?>
=$model->hello?>
2 | 3 | -------------------------------------------------------------------------------- /test/lib/templates/selfContainedTest.php: -------------------------------------------------------------------------------- 1 | 4 |=$hello?>
5 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const php = require('../index') 2 | const request = require('supertest') 3 | const test = require('ava') 4 | const expressSetup = require('./lib/_expressSetup') 5 | 6 | test.before(t => { 7 | const app = expressSetup() 8 | 9 | t.context.app = app 10 | t.context.server = app.listen('43711') 11 | }) 12 | 13 | test.after(t => { 14 | t.context.server.close() 15 | }) 16 | 17 | console.log('General tests:\n') 18 | 19 | test.serial('Executing a PHP script with `run`', async t => { 20 | const res = await php.run('./test/lib/templates/selfContainedTest.php') 21 | t.true(res.includes('world
')) 22 | }) 23 | 24 | test.serial('Executing a PHP script with `runWithData` and passing it some data', async t => { 25 | const res = await php.runWithData('./test/lib/templates/basicTest.php', { hello: 'world' }) 26 | t.true(res.includes('world
')) 27 | }) 28 | 29 | test.serial('Executing a PHP script that has a coding error with `run`', async t => { 30 | try { 31 | await php.run('./test/lib/templates/codingError.php') 32 | } catch (e) { 33 | t.true(e.message.includes('PHP process exited with code 255')) 34 | } 35 | }) 36 | 37 | test.serial('Executing a PHP script that has a coding error with `runWithData`', async t => { 38 | try { 39 | await php.runWithData('./test/lib/templates/codingError.php') 40 | } catch (e) { 41 | t.true(e.message.includes('PHP process exited with code 255')) 42 | } 43 | }) 44 | 45 | console.log('\nExpress server tests:\n') 46 | 47 | test.serial('Passing a model variable down from Express to PHP and getting it to render as a registered global', async t => { 48 | const res = await request(t.context.app).get('/defaults') 49 | t.true(res.text.includes('world
')) 50 | }) 51 | 52 | test.serial('Passing a model variable down from Express to PHP and getting it to render as a registered global, then calling a callback function', async t => { 53 | const res = await request(t.context.app).get('/defaultsWithCallbackFunction') 54 | t.true(res.text.includes('world
')) 55 | }) 56 | 57 | test.serial('Passing a model variable down from Express to a PHP template that has a coding error', async t => { 58 | const res = await request(t.context.app).get('/codingError') 59 | t.true(res.text.includes('Error: PHP process exited with code 255')) 60 | }) 61 | 62 | test.serial('Passing a model down from Express to PHP with the _REGISTER_GLOBAL_MODEL feature disabled at the model level', async t => { 63 | const res = await request(t.context.app).get('/disableRegisterGlobalModelAtModelLevel') 64 | t.true(res.text.includes('world
')) 65 | }) 66 | 67 | test.serial('Passing a model down from Express to PHP with the _REGISTER_GLOBAL_MODEL feature disabled globally', async t => { 68 | const res = await request(t.context.app).get('/disableRegisterGlobalModelGlobally') 69 | t.true(res.text.includes('world
')) 70 | }) 71 | 72 | test.serial('Passing a model down from Express to PHP with the _REGISTER_GLOBAL_MODEL feature disabled globally then reenabled', async t => { 73 | const res = await request(t.context.app).get('/disableRegisterGlobalModelGloballyThenReenabled') 74 | t.true(res.text.includes('world
world
')) 75 | }) 76 | --------------------------------------------------------------------------------