Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:

strategy:
matrix:
node-version: [14, 16, 18]
node-version: [16, 18, 20]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
Expand Down
14 changes: 2 additions & 12 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,16 @@ on:
types: [created]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm test

publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build --if-present
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ build/Release
node_modules/
jspm_packages/

# TypeScript v1 declaration files
# TypeScript declaration files
typings/
index.d.ts
index.d.ts.map

# Optional npm cache directory
.npm
Expand Down
5 changes: 5 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
.github
.idea
node_modules
tsconfig.json
59 changes: 32 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

[![Node.js CI](https://github.com/bartvanraaij/php-array-reader/actions/workflows/node.js.yml/badge.svg)](https://github.com/bartvanraaij/php-array-reader/actions/workflows/node.js.yml)

This small JS utility reads PHP files and strings containing arrays and returns a JavaScript object.
This small JS utility reads PHP strings containing arrays and returns a JavaScript object.

It uses [glayzzle/php-parser](https://github.com/glayzzle/php-parser) to parse PHP into AST and uses that
info to extract arrays.
It supports both indexed and associative arrays and array, string, numeric and null values.
It supports both indexed and associative arrays (i.e. lists and dictionaries/maps) and array, string, numeric and null values.

## Installation

Expand All @@ -18,57 +18,70 @@ npm install php-array-reader --save

## Usage

### With a PHP string
```js
const phpArrayReader = require('php-array-reader');
import { fromString } from 'php-array-reader';

const phpString = `[
'key' => 'string',
'indexed_array' => [
'list' => [
'first',
'second'
],
'associative_array' => [
'dictionary' => [
'foo' => 'bar',
'hello' => 'world'
],
'also_supports' => null,
'and_numeric' => 42
'and_numeric' => 42,
'what_about' => true,
'or' => false,
]`;
const data = phpArrayReader.fromString(phpString);
const data = fromString(phpString);
```
`data` will be this JS object:
```js
{
key: 'string',
indexed_array: ['first', 'second'],
associative_array: {
list: ['first', 'second'],
dictionary: {
foo: 'bar',
hello: 'world'
},
also_supports: null,
and_numeric: 42
and_numeric: 42,
what_about: true,
or: false
}
```

### With a PHP file

Use [`fs.readFileSync`](https://nodejs.org/api/fs.html#fsreadfilesyncpath-options) or another file reading library to read the file, and pass
that string into `fromString`, e.g.:
```js
const phpArrayReader = require('php-array-reader');
import { fromString } from 'php-array-reader';
import { readFileSync } from 'node:fs';

const phpFile = './file.php';
const data = phpArrayReader.fromFile(phpFile);
const phpString = readFileSync(phpFile);

const data = fromString(phpFile);
```

> [!NOTE]
> Version `1.x` of this library included a [`fromFile` method](https://github.com/bartvanraaij/php-array-reader/blob/a3f48acdef4eace2106ac40fa3c4593ab196dc1c/index.js#L6)
> that allowed you to read a file directly. This has been removed in version `2.x` forward, because that method was a scope creep.

The PHP file can either return a single array, e.g.:
```php
<?php
return [
'key' => 'string',
'indexed_array' => [
'list' => [
'first',
'second'
],
'associative_array' => [
'dictionary' => [
'foo' => 'bar',
'hello' => 'world'
],
Expand All @@ -84,13 +97,13 @@ Or the PHP file may consist of multiple assigned arrays, e.g.:
<?php
$first = [
'key' => 'string',
'associative_array' => [
'dictionary' => [
'foo' => 'bar',
'hello' => 'world'
]
];
$second = [
'index_array' => [
'list' => [
'first','second'
],
'also_supports' => null,
Expand All @@ -103,23 +116,15 @@ This will return a JS object with the variable names as the first level keys:
{
first: {
key: 'string',
associative_array: {
dictionary: {
foo: 'bar',
hello: 'world'
}
},
second: {
index_array: ['first', 'second'],
list: ['first', 'second'],
also_supports: null,
and_numeric: 42
}
}
```
You can then of course also use destructuring to assign the results to two variables:
```js
const phpArrayReader = require('php-array-reader');

const phpFile = './file.php';
const { first, second } = phpArrayReader.fromFile(phpFile);
```

186 changes: 85 additions & 101 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,97 @@
const phpParser = require('php-parser'),
fs = require('fs');
'use strict';

const readPhpArray = {
const PhpParser = require('php-parser');

fromFile: function(file) {
const phpString = fs.readFileSync(file, 'utf8');
return readPhpArray.fromString(phpString);
},
/**
* Parse a string containing a PHP array into a JS object
* @param {string} phpString - String containing a PHP array
* @return {Object} The parsed object.
*/
function fromString (phpString) {
const parser = new PhpParser({
parser: {
extractDoc: false,
suppressErrors: true
},
ast: { withPositions: false }
});

fromString: function(phpString){

const parser = new phpParser({
parser: {
extractDoc: false,
suppressErrors: true
},
ast: { withPositions: false },
});

phpString = phpString.trim();
if(phpString.substr(0,5)!=='<?php') {
phpString = '<?php \n'+phpString;
}

const ast = parser.parseCode(phpString);

let phpObject = {};
if (ast.kind === 'program') {

ast.children.forEach(child => {
phpString = phpString.trim();
if (phpString.substring(0, 5) !== '<?php') {
phpString = '<?php \n' + phpString;
}

if(child.kind==='expressionstatement' && child.expression.operator === '=' && child.expression.left.kind === 'variable' && child.expression.right.kind === 'array') {
phpObject[child.expression.left.name] = readPhpArray.parseValue(child.expression.right);
}
else if(child.kind==='expressionstatement' && child.expression.kind === 'array'){
phpObject = readPhpArray.parseValue(child.expression);
}
else if(child.kind === 'return' && child.expr.kind === 'array') {
phpObject = readPhpArray.parseValue(child.expr);
}
const ast = parser.parseCode(phpString);

});
let phpObject = {};
if (ast.kind === 'program') {
ast.children.forEach(child => {
if (child.kind === 'expressionstatement' && child.expression.operator === '=' && child.expression.left.kind === 'variable' && child.expression.right.kind === 'array') {
phpObject[child.expression.left.name] = parseValue(child.expression.right);
} else if (child.kind === 'expressionstatement' && child.expression.kind === 'array') {
phpObject = parseValue(child.expression);
} else if (child.kind === 'return' && child.expr.kind === 'array') {
phpObject = parseValue(child.expr);
}
});
}
return phpObject;
}

/**
* Parse a PHP expression to JavaScript
* @private
* @param {Object} expr The AST PHP expression.
* @return {*} A JavaScript object or value.
*/
function parseValue (expr) {
if (expr === null) return;
if (expr.kind === 'array') {
if (expr.items.length === 0) {
return [];
}
return phpObject;
},

/**
* Parse a PHP expression to JavaScript
* @param {Object} expr The AST PHP expression.
* @return {*} A JavaScript object or value.
*/
parseValue: function(expr) {
if(expr===null) return;
switch(expr.kind) {
case 'array':
if (expr.items.length === 0) {
return [];
}
const isKeyed = expr.items.every(item =>
item === null || item.value === undefined || (item.key!==undefined && item.key !== null)
);
let items = expr.items.map(readPhpArray.parseValue).filter(itm => itm !== undefined);
if (isKeyed) {
items = items.reduce((acc, val) => Object.assign({}, acc, val), {})
}
return items;
case 'entry':
if (expr.key) {
return {
[readPhpArray.parseKey(expr.key)]: readPhpArray.parseValue(expr.value)
}
}
return readPhpArray.parseValue(expr.value);
case 'string':
return expr.value;
case 'number':
return parseFloat(expr.value);
case 'boolean':
return expr.value;
case 'nullkeyword':
return null;
break;
case 'identifier':
if(expr.name.name==='null') {
return null;
}
break;
const isKeyed = expr.items.every(item =>
item === null || item.value === undefined || (item.key !== undefined && item.key !== null)
);
let items = expr.items.map(parseValue).filter(itm => itm !== undefined);
if (isKeyed) {
items = items.reduce((acc, val) => Object.assign({}, acc, val), {});
}
},

/**
* Parse a PHP expression to JavaScript
* @param {Object} expr The AST PHP expression.
* @return {*} A JavaScript object or value.
*/
parseKey: function(expr) {
switch(expr.kind) {
case 'string':
return expr.value;
case 'number':
return parseFloat(expr.value);
case 'boolean':
return expr.value ? 1 : 0;
default:
return null;
return items;
}
if (expr.kind === 'entry') {
if (expr.key) {
return {
[parseKey(expr.key)]: parseValue(expr.value)
};
}
return parseValue(expr.value);
}
if (expr.kind === 'string') return expr.value;
if (expr.kind === 'number') return parseFloat(expr.value);
if (expr.kind === 'boolean') return expr.value;
if (expr.kind === 'nullkeyword') return null;
if (expr.kind === 'identifier' && expr.name.name === 'null') return null;
return undefined;
}

};
/**
* Parse a PHP expression to JavaScript
* @private
* @param {Object} expr The AST PHP expression.
* @return {*} A JavaScript object or value.
*/
function parseKey (expr) {
switch (expr.kind) {
case 'string':
return expr.value;
case 'number':
return parseFloat(expr.value);
case 'boolean':
return expr.value ? 1 : 0;
default:
return null;
}
}

module.exports = readPhpArray;
module.exports = { fromString };
Loading