seeder-migration-issues

This commit is contained in:
RafficMohammed
2023-01-30 14:23:34 +05:30
parent 4d918c722f
commit 2ec836b447
3628 changed files with 116006 additions and 187 deletions

View File

@@ -0,0 +1,43 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 2.6.0 - 2015-11-18
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- [#5](https://github.com/zendframework/zend-json/pull/5) removes
zendframework/zend-stdlib as a required dependency, marking it instead
optional, as it is only used for the `Server` subcomponent.
### Fixed
- Nothing.
## 2.5.2 - 2015-08-05
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#3](https://github.com/zendframework/zend-json/pull/3) fixes an array key
name from `intent` to `indent` to ensure indentation works correctly during
pretty printing.

View File

@@ -0,0 +1,229 @@
# CONTRIBUTING
## RESOURCES
If you wish to contribute to Zend Framework, please be sure to
read/subscribe to the following resources:
- [Coding Standards](https://github.com/zendframework/zf2/wiki/Coding-Standards)
- [Contributor's Guide](http://framework.zend.com/participate/contributor-guide)
- ZF Contributor's mailing list:
Archives: http://zend-framework-community.634137.n4.nabble.com/ZF-Contributor-f680267.html
Subscribe: zf-contributors-subscribe@lists.zend.com
- ZF Contributor's IRC channel:
#zftalk.dev on Freenode.net
If you are working on new features or refactoring [create a proposal](https://github.com/zendframework/zend-json/issues/new).
## Reporting Potential Security Issues
If you have encountered a potential security vulnerability, please **DO NOT** report it on the public
issue tracker: send it to us at [zf-security@zend.com](mailto:zf-security@zend.com) instead.
We will work with you to verify the vulnerability and patch it as soon as possible.
When reporting issues, please provide the following information:
- Component(s) affected
- A description indicating how to reproduce the issue
- A summary of the security vulnerability and impact
We request that you contact us via the email address above and give the project
contributors a chance to resolve the vulnerability and issue a new release prior
to any public exposure; this helps protect users and provides them with a chance
to upgrade and/or update in order to protect their applications.
For sensitive email communications, please use [our PGP key](http://framework.zend.com/zf-security-pgp-key.asc).
## RUNNING TESTS
> ### Note: testing versions prior to 2.4
>
> This component originates with Zend Framework 2. During the lifetime of ZF2,
> testing infrastructure migrated from PHPUnit 3 to PHPUnit 4. In most cases, no
> changes were necessary. However, due to the migration, tests may not run on
> versions < 2.4. As such, you may need to change the PHPUnit dependency if
> attempting a fix on such a version.
To run tests:
- Clone the repository:
```console
$ git clone git@github.com:zendframework/zend-json.git
$ cd
```
- Install dependencies via composer:
```console
$ curl -sS https://getcomposer.org/installer | php --
$ ./composer.phar install
```
If you don't have `curl` installed, you can also download `composer.phar` from https://getcomposer.org/
- Run the tests via `phpunit` and the provided PHPUnit config, like in this example:
```console
$ ./vendor/bin/phpunit
```
You can turn on conditional tests with the phpunit.xml file.
To do so:
- Copy `phpunit.xml.dist` file to `phpunit.xml`
- Edit `phpunit.xml` to enable any specific functionality you
want to test, as well as to provide test values to utilize.
## Running Coding Standards Checks
This component uses [php-cs-fixer](http://cs.sensiolabs.org/) for coding
standards checks, and provides configuration for our selected checks.
`php-cs-fixer` is installed by default via Composer.
To run checks only:
```console
$ ./vendor/bin/php-cs-fixer fix . -v --diff --dry-run --config-file=.php_cs
```
To have `php-cs-fixer` attempt to fix problems for you, omit the `--dry-run`
flag:
```console
$ ./vendor/bin/php-cs-fixer fix . -v --diff --config-file=.php_cs
```
If you allow php-cs-fixer to fix CS issues, please re-run the tests to ensure
they pass, and make sure you add and commit the changes after verification.
## Recommended Workflow for Contributions
Your first step is to establish a public repository from which we can
pull your work into the master repository. We recommend using
[GitHub](https://github.com), as that is where the component is already hosted.
1. Setup a [GitHub account](http://github.com/), if you haven't yet
2. Fork the repository (http://github.com/zendframework/zend-json)
3. Clone the canonical repository locally and enter it.
```console
$ git clone git://github.com:zendframework/zend-json.git
$ cd zend-json
```
4. Add a remote to your fork; substitute your GitHub username in the command
below.
```console
$ git remote add {username} git@github.com:{username}/zend-json.git
$ git fetch {username}
```
### Keeping Up-to-Date
Periodically, you should update your fork or personal repository to
match the canonical ZF repository. Assuming you have setup your local repository
per the instructions above, you can do the following:
```console
$ git checkout master
$ git fetch origin
$ git rebase origin/master
# OPTIONALLY, to keep your remote up-to-date -
$ git push {username} master:master
```
If you're tracking other branches -- for example, the "develop" branch, where
new feature development occurs -- you'll want to do the same operations for that
branch; simply substitute "develop" for "master".
### Working on a patch
We recommend you do each new feature or bugfix in a new branch. This simplifies
the task of code review as well as the task of merging your changes into the
canonical repository.
A typical workflow will then consist of the following:
1. Create a new local branch based off either your master or develop branch.
2. Switch to your new local branch. (This step can be combined with the
previous step with the use of `git checkout -b`.)
3. Do some work, commit, repeat as necessary.
4. Push the local branch to your remote repository.
5. Send a pull request.
The mechanics of this process are actually quite trivial. Below, we will
create a branch for fixing an issue in the tracker.
```console
$ git checkout -b hotfix/9295
Switched to a new branch 'hotfix/9295'
```
... do some work ...
```console
$ git commit
```
... write your log message ...
```console
$ git push {username} hotfix/9295:hotfix/9295
Counting objects: 38, done.
Delta compression using up to 2 threads.
Compression objects: 100% (18/18), done.
Writing objects: 100% (20/20), 8.19KiB, done.
Total 20 (delta 12), reused 0 (delta 0)
To ssh://git@github.com/{username}/zend-json.git
b5583aa..4f51698 HEAD -> master
```
To send a pull request, you have two options.
If using GitHub, you can do the pull request from there. Navigate to
your repository, select the branch you just created, and then select the
"Pull Request" button in the upper right. Select the user/organization
"zendframework" as the recipient.
If using your own repository - or even if using GitHub - you can use `git
format-patch` to create a patchset for us to apply; in fact, this is
**recommended** for security-related patches. If you use `format-patch`, please
send the patches as attachments to:
- zf-devteam@zend.com for patches without security implications
- zf-security@zend.com for security patches
#### What branch to issue the pull request against?
Which branch should you issue a pull request against?
- For fixes against the stable release, issue the pull request against the
"master" branch.
- For new features, or fixes that introduce new elements to the public API (such
as new public methods or properties), issue the pull request against the
"develop" branch.
### Branch Cleanup
As you might imagine, if you are a frequent contributor, you'll start to
get a ton of branches both locally and on your remote.
Once you know that your changes have been accepted to the master
repository, we suggest doing some cleanup of these branches.
- Local branch cleanup
```console
$ git branch -d <branchname>
```
- Remote branch removal
```console
$ git push {username} :<branchname>
```

View File

@@ -0,0 +1,28 @@
Copyright (c) 2005-2015, Zend Technologies USA, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Zend Technologies USA, Inc. nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,12 @@
# zend-json
[![Build Status](https://secure.travis-ci.org/zendframework/zend-json.svg?branch=master)](https://secure.travis-ci.org/zendframework/zend-json)
[![Coverage Status](https://coveralls.io/repos/zendframework/zend-json/badge.svg?branch=master)](https://coveralls.io/r/zendframework/zend-json?branch=master)
`Zend\Json` provides convenience methods for serializing native PHP to JSON and
decoding JSON to native PHP. For more information on JSON, visit the JSON
[project site](http://www.json.org/).
- File issues at https://github.com/zendframework/zend-json/issues
- Documentation is at http://framework.zend.com/manual/current/en/index.html#zend-json

View File

@@ -0,0 +1,45 @@
{
"name": "zendframework/zend-json",
"description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP",
"license": "BSD-3-Clause",
"keywords": [
"zf2",
"json"
],
"homepage": "https://github.com/zendframework/zend-json",
"autoload": {
"psr-4": {
"Zend\\Json\\": "src/"
}
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"zendframework/zend-http": "~2.5",
"zendframework/zend-server": "~2.5",
"zendframework/zend-stdlib": "~2.5",
"zendframework/zendxml": "~1.0",
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/PHPUnit": "~4.0"
},
"suggest": {
"zendframework/zend-http": "Zend\\Http component",
"zendframework/zend-server": "Zend\\Server component",
"zendframework/zend-stdlib": "To use the cache for Zend\\Server",
"zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage"
},
"minimum-stability": "dev",
"prefer-stable": true,
"extra": {
"branch-alias": {
"dev-master": "2.6-dev",
"dev-develop": "2.7-dev"
}
},
"autoload-dev": {
"psr-4": {
"ZendTest\\Json\\": "test/"
}
}
}

View File

@@ -0,0 +1,542 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json;
use stdClass;
use Zend\Json\Exception\InvalidArgumentException;
use Zend\Json\Exception\RuntimeException;
/**
* Decode JSON encoded string to PHP variable constructs
*/
class Decoder
{
/**
* Parse tokens used to decode the JSON object. These are not
* for public consumption, they are just used internally to the
* class.
*/
const EOF = 0;
const DATUM = 1;
const LBRACE = 2;
const LBRACKET = 3;
const RBRACE = 4;
const RBRACKET = 5;
const COMMA = 6;
const COLON = 7;
/**
* Use to maintain a "pointer" to the source being decoded
*
* @var string
*/
protected $source;
/**
* Caches the source length
*
* @var int
*/
protected $sourceLength;
/**
* The offset within the source being decoded
*
* @var int
*
*/
protected $offset;
/**
* The current token being considered in the parser cycle
*
* @var int
*/
protected $token;
/**
* Flag indicating how objects should be decoded
*
* @var int
* @access protected
*/
protected $decodeType;
/**
* @var $_tokenValue
*/
protected $tokenValue;
/**
* Decode Unicode Characters from \u0000 ASCII syntax.
*
* This algorithm was originally developed for the
* Solar Framework by Paul M. Jones
*
* @link http://solarphp.com/
* @link https://github.com/solarphp/core/blob/master/Solar/Json.php
* @param string $chrs
* @return string
*/
public static function decodeUnicodeString($chrs)
{
$chrs = (string) $chrs;
$utf8 = '';
$strlenChrs = strlen($chrs);
for ($i = 0; $i < $strlenChrs; $i++) {
$ordChrsC = ord($chrs[$i]);
switch (true) {
case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $i, 6)):
// single, escaped unicode character
$utf16 = chr(hexdec(substr($chrs, ($i + 2), 2)))
. chr(hexdec(substr($chrs, ($i + 4), 2)));
$utf8char = self::_utf162utf8($utf16);
$search = ['\\', "\n", "\t", "\r", chr(0x08), chr(0x0C), '"', '\'', '/'];
if (in_array($utf8char, $search)) {
$replace = ['\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\\"', '\\\'', '\\/'];
$utf8char = str_replace($search, $replace, $utf8char);
}
$utf8 .= $utf8char;
$i += 5;
break;
case ($ordChrsC >= 0x20) && ($ordChrsC <= 0x7F):
$utf8 .= $chrs{$i};
break;
case ($ordChrsC & 0xE0) == 0xC0:
// characters U-00000080 - U-000007FF, mask 110XXXXX
//see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $i, 2);
++$i;
break;
case ($ordChrsC & 0xF0) == 0xE0:
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $i, 3);
$i += 2;
break;
case ($ordChrsC & 0xF8) == 0xF0:
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $i, 4);
$i += 3;
break;
case ($ordChrsC & 0xFC) == 0xF8:
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $i, 5);
$i += 4;
break;
case ($ordChrsC & 0xFE) == 0xFC:
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$utf8 .= substr($chrs, $i, 6);
$i += 5;
break;
}
}
return $utf8;
}
/**
* Constructor
*
* @param string $source String source to decode
* @param int $decodeType How objects should be decoded -- see
* {@link Zend\Json\Json::TYPE_ARRAY} and {@link Zend\Json\Json::TYPE_OBJECT} for
* valid values
* @throws InvalidArgumentException
*/
protected function __construct($source, $decodeType)
{
// Set defaults
$this->source = self::decodeUnicodeString($source);
$this->sourceLength = strlen($this->source);
$this->token = self::EOF;
$this->offset = 0;
switch ($decodeType) {
case Json::TYPE_ARRAY:
case Json::TYPE_OBJECT:
$this->decodeType = $decodeType;
break;
default:
throw new InvalidArgumentException("Unknown decode type '{$decodeType}', please use one of the constants Json::TYPE_*");
}
// Set pointer at first token
$this->_getNextToken();
}
/**
* Decode a JSON source string
*
* Decodes a JSON encoded string. The value returned will be one of the
* following:
* - integer
* - float
* - boolean
* - null
* - stdClass
* - array
* - array of one or more of the above types
*
* By default, decoded objects will be returned as associative arrays; to
* return a stdClass object instead, pass {@link Zend\Json\Json::TYPE_OBJECT} to
* the $objectDecodeType parameter.
*
* @static
* @access public
* @param string $source String to be decoded
* @param int $objectDecodeType How objects should be decoded; should be
* either or {@link Zend\Json\Json::TYPE_ARRAY} or
* {@link Zend\Json\Json::TYPE_OBJECT}; defaults to TYPE_ARRAY
* @return mixed
*/
public static function decode($source, $objectDecodeType = Json::TYPE_OBJECT)
{
$decoder = new static($source, $objectDecodeType);
return $decoder->_decodeValue();
}
/**
* Recursive driving routine for supported toplevel tops
*
* @return mixed
*/
protected function _decodeValue()
{
switch ($this->token) {
case self::DATUM:
$result = $this->tokenValue;
$this->_getNextToken();
return($result);
case self::LBRACE:
return($this->_decodeObject());
case self::LBRACKET:
return($this->_decodeArray());
default:
return;
}
}
/**
* Decodes an object of the form:
* { "attribute: value, "attribute2" : value,...}
*
* If Zend\Json\Encoder was used to encode the original object then
* a special attribute called __className which specifies a class
* name that should wrap the data contained within the encoded source.
*
* Decodes to either an array or stdClass object, based on the value of
* {@link $decodeType}. If invalid $decodeType present, returns as an
* array.
*
* @return array|stdClass
* @throws RuntimeException
*/
protected function _decodeObject()
{
$members = [];
$tok = $this->_getNextToken();
while ($tok && $tok != self::RBRACE) {
if ($tok != self::DATUM || ! is_string($this->tokenValue)) {
throw new RuntimeException('Missing key in object encoding: ' . $this->source);
}
$key = $this->tokenValue;
$tok = $this->_getNextToken();
if ($tok != self::COLON) {
throw new RuntimeException('Missing ":" in object encoding: ' . $this->source);
}
$this->_getNextToken();
$members[$key] = $this->_decodeValue();
$tok = $this->token;
if ($tok == self::RBRACE) {
break;
}
if ($tok != self::COMMA) {
throw new RuntimeException('Missing "," in object encoding: ' . $this->source);
}
$tok = $this->_getNextToken();
}
switch ($this->decodeType) {
case Json::TYPE_OBJECT:
// Create new stdClass and populate with $members
$result = new stdClass();
foreach ($members as $key => $value) {
if ($key === '') {
$key = '_empty_';
}
$result->$key = $value;
}
break;
case Json::TYPE_ARRAY:
default:
$result = $members;
break;
}
$this->_getNextToken();
return $result;
}
/**
* Decodes a JSON array format:
* [element, element2,...,elementN]
*
* @return array
* @throws RuntimeException
*/
protected function _decodeArray()
{
$result = [];
$tok = $this->_getNextToken(); // Move past the '['
$index = 0;
while ($tok && $tok != self::RBRACKET) {
$result[$index++] = $this->_decodeValue();
$tok = $this->token;
if ($tok == self::RBRACKET || !$tok) {
break;
}
if ($tok != self::COMMA) {
throw new RuntimeException('Missing "," in array encoding: ' . $this->source);
}
$tok = $this->_getNextToken();
}
$this->_getNextToken();
return $result;
}
/**
* Removes whitespace characters from the source input
*/
protected function _eatWhitespace()
{
if (preg_match('/([\t\b\f\n\r ])*/s', $this->source, $matches, PREG_OFFSET_CAPTURE, $this->offset)
&& $matches[0][1] == $this->offset) {
$this->offset += strlen($matches[0][0]);
}
}
/**
* Retrieves the next token from the source stream
*
* @return int Token constant value specified in class definition
* @throws RuntimeException
*/
protected function _getNextToken()
{
$this->token = self::EOF;
$this->tokenValue = null;
$this->_eatWhitespace();
if ($this->offset >= $this->sourceLength) {
return(self::EOF);
}
$str = $this->source;
$strLength = $this->sourceLength;
$i = $this->offset;
$start = $i;
switch ($str{$i}) {
case '{':
$this->token = self::LBRACE;
break;
case '}':
$this->token = self::RBRACE;
break;
case '[':
$this->token = self::LBRACKET;
break;
case ']':
$this->token = self::RBRACKET;
break;
case ',':
$this->token = self::COMMA;
break;
case ':':
$this->token = self::COLON;
break;
case '"':
$result = '';
do {
$i++;
if ($i >= $strLength) {
break;
}
$chr = $str{$i};
if ($chr == '\\') {
$i++;
if ($i >= $strLength) {
break;
}
$chr = $str{$i};
switch ($chr) {
case '"':
$result .= '"';
break;
case '\\':
$result .= '\\';
break;
case '/':
$result .= '/';
break;
case 'b':
$result .= "\x08";
break;
case 'f':
$result .= "\x0c";
break;
case 'n':
$result .= "\x0a";
break;
case 'r':
$result .= "\x0d";
break;
case 't':
$result .= "\x09";
break;
case '\'':
$result .= '\'';
break;
default:
throw new RuntimeException("Illegal escape sequence '{$chr}'");
}
} elseif ($chr == '"') {
break;
} else {
$result .= $chr;
}
} while ($i < $strLength);
$this->token = self::DATUM;
//$this->tokenValue = substr($str, $start + 1, $i - $start - 1);
$this->tokenValue = $result;
break;
case 't':
if (($i+ 3) < $strLength && substr($str, $start, 4) == "true") {
$this->token = self::DATUM;
}
$this->tokenValue = true;
$i += 3;
break;
case 'f':
if (($i+ 4) < $strLength && substr($str, $start, 5) == "false") {
$this->token = self::DATUM;
}
$this->tokenValue = false;
$i += 4;
break;
case 'n':
if (($i+ 3) < $strLength && substr($str, $start, 4) == "null") {
$this->token = self::DATUM;
}
$this->tokenValue = null;
$i += 3;
break;
}
if ($this->token != self::EOF) {
$this->offset = $i + 1; // Consume the last token character
return($this->token);
}
$chr = $str{$i};
if ($chr == '-' || $chr == '.' || ($chr >= '0' && $chr <= '9')) {
if (preg_match('/-?([0-9])*(\.[0-9]*)?((e|E)((-|\+)?)[0-9]+)?/s', $str, $matches, PREG_OFFSET_CAPTURE, $start) && $matches[0][1] == $start) {
$datum = $matches[0][0];
if (is_numeric($datum)) {
if (preg_match('/^0\d+$/', $datum)) {
throw new RuntimeException("Octal notation not supported by JSON (value: {$datum})");
} else {
$val = intval($datum);
$fVal = floatval($datum);
$this->tokenValue = ($val == $fVal ? $val : $fVal);
}
} else {
throw new RuntimeException("Illegal number format: {$datum}");
}
$this->token = self::DATUM;
$this->offset = $start + strlen($datum);
}
} else {
throw new RuntimeException('Illegal Token');
}
return $this->token;
}
/**
* Convert a string from one UTF-16 char to one UTF-8 char.
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibyte string extension.
*
* This method is from the Solar Framework by Paul M. Jones
*
* @link http://solarphp.com
* @param string $utf16 UTF-16 character
* @return string UTF-8 character
*/
protected static function _utf162utf8($utf16)
{
// Check for mb extension otherwise do by hand.
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
}
$bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
switch (true) {
case ((0x7F & $bytes) == $bytes):
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x7F & $bytes);
case (0x07FF & $bytes) == $bytes:
// return a 2-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xC0 | (($bytes >> 6) & 0x1F))
. chr(0x80 | ($bytes & 0x3F));
case (0xFFFF & $bytes) == $bytes:
// return a 3-byte UTF-8 character
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0xE0 | (($bytes >> 12) & 0x0F))
. chr(0x80 | (($bytes >> 6) & 0x3F))
. chr(0x80 | ($bytes & 0x3F));
}
// ignoring UTF-32 for now, sorry
return '';
}
}

View File

@@ -0,0 +1,568 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json;
use Iterator;
use IteratorAggregate;
use JsonSerializable;
use ReflectionClass;
use Zend\Json\Exception\InvalidArgumentException;
use Zend\Json\Exception\RecursionException;
/**
* Encode PHP constructs to JSON
*/
class Encoder
{
/**
* Whether or not to check for possible cycling
*
* @var bool
*/
protected $cycleCheck;
/**
* Additional options used during encoding
*
* @var array
*/
protected $options = [];
/**
* Array of visited objects; used to prevent cycling.
*
* @var array
*/
protected $visited = [];
/**
* Constructor
*
* @param bool $cycleCheck Whether or not to check for recursion when encoding
* @param array $options Additional options used during encoding
* @return Encoder
*/
protected function __construct($cycleCheck = false, $options = [])
{
$this->cycleCheck = $cycleCheck;
$this->options = $options;
}
/**
* Use the JSON encoding scheme for the value specified
*
* @param mixed $value The value to be encoded
* @param bool $cycleCheck Whether or not to check for possible object recursion when encoding
* @param array $options Additional options used during encoding
* @return string The encoded value
*/
public static function encode($value, $cycleCheck = false, $options = [])
{
$encoder = new static($cycleCheck, $options);
if ($value instanceof JsonSerializable) {
$value = $value->jsonSerialize();
}
return $encoder->_encodeValue($value);
}
/**
* Recursive driver which determines the type of value to be encoded
* and then dispatches to the appropriate method. $values are either
* - objects (returns from {@link _encodeObject()})
* - arrays (returns from {@link _encodeArray()})
* - basic datums (e.g. numbers or strings) (returns from {@link _encodeDatum()})
*
* @param $value mixed The value to be encoded
* @return string Encoded value
*/
protected function _encodeValue(&$value)
{
if (is_object($value)) {
return $this->_encodeObject($value);
} elseif (is_array($value)) {
return $this->_encodeArray($value);
}
return $this->_encodeDatum($value);
}
/**
* Encode an object to JSON by encoding each of the public properties
*
* A special property is added to the JSON object called '__className'
* that contains the name of the class of $value. This is used to decode
* the object on the client into a specific class.
*
* @param $value object
* @return string
* @throws RecursionException If recursive checks are enabled and the
* object has been serialized previously
*/
protected function _encodeObject(&$value)
{
if ($this->cycleCheck) {
if ($this->_wasVisited($value)) {
if (isset($this->options['silenceCyclicalExceptions'])
&& $this->options['silenceCyclicalExceptions']===true) {
return '"* RECURSION (' . str_replace('\\', '\\\\', get_class($value)) . ') *"';
} else {
throw new RecursionException(
'Cycles not supported in JSON encoding, cycle introduced by '
. 'class "' . get_class($value) . '"'
);
}
}
$this->visited[] = $value;
}
$props = '';
if (method_exists($value, 'toJson')) {
$props = ',' . preg_replace("/^\{(.*)\}$/", "\\1", $value->toJson());
} else {
if ($value instanceof IteratorAggregate) {
$propCollection = $value->getIterator();
} elseif ($value instanceof Iterator) {
$propCollection = $value;
} else {
$propCollection = get_object_vars($value);
}
foreach ($propCollection as $name => $propValue) {
if (isset($propValue)) {
$props .= ','
. $this->_encodeValue($name)
. ':'
. $this->_encodeValue($propValue);
}
}
}
$className = get_class($value);
return '{"__className":'
. $this->_encodeString($className)
. $props . '}';
}
/**
* Determine if an object has been serialized already
*
* @param mixed $value
* @return bool
*/
protected function _wasVisited(&$value)
{
if (in_array($value, $this->visited, true)) {
return true;
}
return false;
}
/**
* JSON encode an array value
*
* Recursively encodes each value of an array and returns a JSON encoded
* array string.
*
* Arrays are defined as integer-indexed arrays starting at index 0, where
* the last index is (count($array) -1); any deviation from that is
* considered an associative array, and will be encoded as such.
*
* @param $array array
* @return string
*/
protected function _encodeArray(&$array)
{
$tmpArray = [];
// Check for associative array
if (!empty($array) && (array_keys($array) !== range(0, count($array) - 1))) {
// Associative array
$result = '{';
foreach ($array as $key => $value) {
$key = (string) $key;
$tmpArray[] = $this->_encodeString($key)
. ':'
. $this->_encodeValue($value);
}
$result .= implode(',', $tmpArray);
$result .= '}';
} else {
// Indexed array
$result = '[';
$length = count($array);
for ($i = 0; $i < $length; $i++) {
$tmpArray[] = $this->_encodeValue($array[$i]);
}
$result .= implode(',', $tmpArray);
$result .= ']';
}
return $result;
}
/**
* JSON encode a basic data type (string, number, boolean, null)
*
* If value type is not a string, number, boolean, or null, the string
* 'null' is returned.
*
* @param mixed $value
* @return string
*/
protected function _encodeDatum(&$value)
{
$result = 'null';
if (is_int($value) || is_float($value)) {
$result = (string) $value;
$result = str_replace(',', '.', $result);
} elseif (is_string($value)) {
$result = $this->_encodeString($value);
} elseif (is_bool($value)) {
$result = $value ? 'true' : 'false';
}
return $result;
}
/**
* JSON encode a string value by escaping characters as necessary
*
* @param string $string
* @return string
*/
protected function _encodeString(&$string)
{
// Escape these characters with a backslash or unicode escape:
// " \ / \n \r \t \b \f
$search = ['\\', "\n", "\t", "\r", "\b", "\f", '"', '\'', '&', '<', '>', '/'];
$replace = ['\\\\', '\\n', '\\t', '\\r', '\\b', '\\f', '\\u0022', '\\u0027', '\\u0026', '\\u003C', '\\u003E', '\\/'];
$string = str_replace($search, $replace, $string);
// Escape certain ASCII characters:
// 0x08 => \b
// 0x0c => \f
$string = str_replace([chr(0x08), chr(0x0C)], ['\b', '\f'], $string);
$string = self::encodeUnicodeString($string);
return '"' . $string . '"';
}
/**
* Encode the constants associated with the ReflectionClass
* parameter. The encoding format is based on the class2 format
*
* @param ReflectionClass $cls
* @return string Encoded constant block in class2 format
*/
private static function _encodeConstants(ReflectionClass $cls)
{
$result = "constants : {";
$constants = $cls->getConstants();
$tmpArray = [];
if (!empty($constants)) {
foreach ($constants as $key => $value) {
$tmpArray[] = "$key: " . self::encode($value);
}
$result .= implode(', ', $tmpArray);
}
return $result . "}";
}
/**
* Encode the public methods of the ReflectionClass in the
* class2 format
*
* @param ReflectionClass $cls
* @return string Encoded method fragment
*
*/
private static function _encodeMethods(ReflectionClass $cls)
{
$methods = $cls->getMethods();
$result = 'methods:{';
$started = false;
foreach ($methods as $method) {
if (! $method->isPublic() || !$method->isUserDefined()) {
continue;
}
if ($started) {
$result .= ',';
}
$started = true;
$result .= '' . $method->getName(). ':function(';
if ('__construct' != $method->getName()) {
$parameters = $method->getParameters();
$argsStarted = false;
$argNames = "var argNames=[";
foreach ($parameters as $param) {
if ($argsStarted) {
$result .= ',';
}
$result .= $param->getName();
if ($argsStarted) {
$argNames .= ',';
}
$argNames .= '"' . $param->getName() . '"';
$argsStarted = true;
}
$argNames .= "];";
$result .= "){"
. $argNames
. 'var result = ZAjaxEngine.invokeRemoteMethod('
. "this, '" . $method->getName()
. "',argNames,arguments);"
. 'return(result);}';
} else {
$result .= "){}";
}
}
return $result . "}";
}
/**
* Encode the public properties of the ReflectionClass in the class2
* format.
*
* @param ReflectionClass $cls
* @return string Encode properties list
*
*/
private static function _encodeVariables(ReflectionClass $cls)
{
$properties = $cls->getProperties();
$propValues = get_class_vars($cls->getName());
$result = "variables:{";
$tmpArray = [];
foreach ($properties as $prop) {
if (! $prop->isPublic()) {
continue;
}
$tmpArray[] = $prop->getName()
. ':'
. self::encode($propValues[$prop->getName()]);
}
$result .= implode(',', $tmpArray);
return $result . "}";
}
/**
* Encodes the given $className into the class2 model of encoding PHP
* classes into JavaScript class2 classes.
* NOTE: Currently only public methods and variables are proxied onto
* the client machine
*
* @param $className string The name of the class, the class must be
* instantiable using a null constructor
* @param $package string Optional package name appended to JavaScript
* proxy class name
* @return string The class2 (JavaScript) encoding of the class
* @throws InvalidArgumentException
*/
public static function encodeClass($className, $package = '')
{
$cls = new \ReflectionClass($className);
if (! $cls->isInstantiable()) {
throw new InvalidArgumentException("'{$className}' must be instantiable");
}
return "Class.create('$package$className',{"
. self::_encodeConstants($cls) .","
. self::_encodeMethods($cls) .","
. self::_encodeVariables($cls) .'});';
}
/**
* Encode several classes at once
*
* Returns JSON encoded classes, using {@link encodeClass()}.
*
* @param array $classNames
* @param string $package
* @return string
*/
public static function encodeClasses(array $classNames, $package = '')
{
$result = '';
foreach ($classNames as $className) {
$result .= static::encodeClass($className, $package);
}
return $result;
}
/**
* Encode Unicode Characters to \u0000 ASCII syntax.
*
* This algorithm was originally developed for the
* Solar Framework by Paul M. Jones
*
* @link http://solarphp.com/
* @link https://github.com/solarphp/core/blob/master/Solar/Json.php
* @param string $value
* @return string
*/
public static function encodeUnicodeString($value)
{
$strlenVar = strlen($value);
$ascii = "";
/**
* Iterate over every character in the string,
* escaping with a slash or encoding to UTF-8 where necessary
*/
for ($i = 0; $i < $strlenVar; $i++) {
$ordVarC = ord($value[$i]);
switch (true) {
case (($ordVarC >= 0x20) && ($ordVarC <= 0x7F)):
// characters U-00000000 - U-0000007F (same as ASCII)
$ascii .= $value[$i];
break;
case (($ordVarC & 0xE0) == 0xC0):
// characters U-00000080 - U-000007FF, mask 110XXXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack('C*', $ordVarC, ord($value[$i + 1]));
$i += 1;
$utf16 = self::_utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ordVarC & 0xF0) == 0xE0):
// characters U-00000800 - U-0000FFFF, mask 1110XXXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack(
'C*',
$ordVarC,
ord($value[$i + 1]),
ord($value[$i + 2])
);
$i += 2;
$utf16 = self::_utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ordVarC & 0xF8) == 0xF0):
// characters U-00010000 - U-001FFFFF, mask 11110XXX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack(
'C*',
$ordVarC,
ord($value[$i + 1]),
ord($value[$i + 2]),
ord($value[$i + 3])
);
$i += 3;
$utf16 = self::_utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ordVarC & 0xFC) == 0xF8):
// characters U-00200000 - U-03FFFFFF, mask 111110XX
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack(
'C*',
$ordVarC,
ord($value[$i + 1]),
ord($value[$i + 2]),
ord($value[$i + 3]),
ord($value[$i + 4])
);
$i += 4;
$utf16 = self::_utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
case (($ordVarC & 0xFE) == 0xFC):
// characters U-04000000 - U-7FFFFFFF, mask 1111110X
// see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
$char = pack(
'C*',
$ordVarC,
ord($value[$i + 1]),
ord($value[$i + 2]),
ord($value[$i + 3]),
ord($value[$i + 4]),
ord($value[$i + 5])
);
$i += 5;
$utf16 = self::_utf82utf16($char);
$ascii .= sprintf('\u%04s', bin2hex($utf16));
break;
}
}
return $ascii;
}
/**
* Convert a string from one UTF-8 char to one UTF-16 char.
*
* Normally should be handled by mb_convert_encoding, but
* provides a slower PHP-only method for installations
* that lack the multibyte string extension.
*
* This method is from the Solar Framework by Paul M. Jones
*
* @link http://solarphp.com
* @param string $utf8 UTF-8 character
* @return string UTF-16 character
*/
protected static function _utf82utf16($utf8)
{
// Check for mb extension otherwise do by hand.
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
}
switch (strlen($utf8)) {
case 1:
// this case should never be reached, because we are in ASCII range
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return $utf8;
case 2:
// return a UTF-16 character from a 2-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr(0x07 & (ord($utf8{0}) >> 2)) . chr((0xC0 & (ord($utf8{0}) << 6)) | (0x3F & ord($utf8{1})));
case 3:
// return a UTF-16 character from a 3-byte UTF-8 char
// see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
return chr((0xF0 & (ord($utf8{0}) << 4)) | (0x0F & (ord($utf8{1}) >> 2))) . chr((0xC0 & (ord($utf8{1}) << 6)) | (0x7F & ord($utf8{2})));
}
// ignoring UTF-32 for now, sorry
return '';
}
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Exception;
class BadMethodCallException extends \BadMethodCallException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Exception;
interface ExceptionInterface
{
}

View File

@@ -0,0 +1,15 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Exception;
class RecursionException extends RuntimeException
{
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json;
/**
* Class for Zend\Json\Json encode method.
*
* This class simply holds a string with a native Javascript Expression,
* so objects | arrays to be encoded with Zend\Json\Json can contain native
* Javascript Expressions.
*
* Example:
* <code>
* $foo = array(
* 'integer' => 9,
* 'string' => 'test string',
* 'function' => Zend\Json\Expr(
* 'function () { window.alert("javascript function encoded by Zend\Json\Json") }'
* ),
* );
*
* Zend\Json\Json::encode($foo, false, array('enableJsonExprFinder' => true));
* // it will returns json encoded string:
* // {"integer":9,"string":"test string","function":function () {window.alert("javascript function encoded by Zend\Json\Json")}}
* </code>
*/
class Expr
{
/**
* Storage for javascript expression.
*
* @var string
*/
protected $expression;
/**
* Constructor
*
* @param string $expression the expression to hold.
*/
public function __construct($expression)
{
$this->expression = (string) $expression;
}
/**
* Cast to string
*
* @return string holded javascript expression.
*/
public function __toString()
{
return $this->expression;
}
}

View File

@@ -0,0 +1,406 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json;
use SimpleXMLElement;
use Zend\Json\Exception\RecursionException;
use Zend\Json\Exception\RuntimeException;
use ZendXml\Security as XmlSecurity;
/**
* Class for encoding to and decoding from JSON.
*/
class Json
{
/**
* How objects should be encoded -- arrays or as stdClass. TYPE_ARRAY is 1
* so that it is a boolean true value, allowing it to be used with
* ext/json's functions.
*/
const TYPE_ARRAY = 1;
const TYPE_OBJECT = 0;
/**
* To check the allowed nesting depth of the XML tree during xml2json conversion.
*
* @var int
*/
public static $maxRecursionDepthAllowed = 25;
/**
* @var bool
*/
public static $useBuiltinEncoderDecoder = false;
/**
* Decodes the given $encodedValue string which is
* encoded in the JSON format
*
* Uses ext/json's json_decode if available.
*
* @param string $encodedValue Encoded in JSON format
* @param int $objectDecodeType Optional; flag indicating how to decode
* objects. See {@link Zend\Json\Decoder::decode()} for details.
* @return mixed
* @throws RuntimeException
*/
public static function decode($encodedValue, $objectDecodeType = self::TYPE_OBJECT)
{
$encodedValue = (string) $encodedValue;
if (function_exists('json_decode') && static::$useBuiltinEncoderDecoder !== true) {
$decode = json_decode($encodedValue, $objectDecodeType);
switch (json_last_error()) {
case JSON_ERROR_NONE:
break;
case JSON_ERROR_DEPTH:
throw new RuntimeException('Decoding failed: Maximum stack depth exceeded');
case JSON_ERROR_CTRL_CHAR:
throw new RuntimeException('Decoding failed: Unexpected control character found');
case JSON_ERROR_SYNTAX:
throw new RuntimeException('Decoding failed: Syntax error');
default:
throw new RuntimeException('Decoding failed');
}
return $decode;
}
return Decoder::decode($encodedValue, $objectDecodeType);
}
/**
* Encode the mixed $valueToEncode into the JSON format
*
* Encodes using ext/json's json_encode() if available.
*
* NOTE: Object should not contain cycles; the JSON format
* does not allow object reference.
*
* NOTE: Only public variables will be encoded
*
* NOTE: Encoding native javascript expressions are possible using Zend\Json\Expr.
* You can enable this by setting $options['enableJsonExprFinder'] = true
*
* @see Zend\Json\Expr
*
* @param mixed $valueToEncode
* @param bool $cycleCheck Optional; whether or not to check for object recursion; off by default
* @param array $options Additional options used during encoding
* @return string JSON encoded object
*/
public static function encode($valueToEncode, $cycleCheck = false, $options = [])
{
if (is_object($valueToEncode)) {
if (method_exists($valueToEncode, 'toJson')) {
return $valueToEncode->toJson();
} elseif (method_exists($valueToEncode, 'toArray')) {
return static::encode($valueToEncode->toArray(), $cycleCheck, $options);
}
}
// Pre-encoding look for Zend\Json\Expr objects and replacing by tmp ids
$javascriptExpressions = [];
if (isset($options['enableJsonExprFinder'])
&& ($options['enableJsonExprFinder'] == true)
) {
$valueToEncode = static::_recursiveJsonExprFinder($valueToEncode, $javascriptExpressions);
}
$prettyPrint = (isset($options['prettyPrint']) && ($options['prettyPrint'] == true));
// Encoding
if (function_exists('json_encode') && static::$useBuiltinEncoderDecoder !== true) {
$encodeOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP;
if ($prettyPrint && defined('JSON_PRETTY_PRINT')) {
$encodeOptions |= JSON_PRETTY_PRINT;
$prettyPrint = false;
}
$encodedResult = json_encode(
$valueToEncode,
$encodeOptions
);
} else {
$encodedResult = Encoder::encode($valueToEncode, $cycleCheck, $options);
}
if ($prettyPrint) {
$encodedResult = self::prettyPrint($encodedResult, ["indent" => " "]);
}
//only do post-processing to revert back the Zend\Json\Expr if any.
if (count($javascriptExpressions) > 0) {
$count = count($javascriptExpressions);
for ($i = 0; $i < $count; $i++) {
$magicKey = $javascriptExpressions[$i]['magicKey'];
$value = $javascriptExpressions[$i]['value'];
$encodedResult = str_replace(
//instead of replacing "key:magicKey", we replace directly magicKey by value because "key" never changes.
'"' . $magicKey . '"',
$value,
$encodedResult
);
}
}
return $encodedResult;
}
/**
* Check & Replace Zend\Json\Expr for tmp ids in the valueToEncode
*
* Check if the value is a Zend\Json\Expr, and if replace its value
* with a magic key and save the javascript expression in an array.
*
* NOTE this method is recursive.
*
* NOTE: This method is used internally by the encode method.
*
* @see encode
* @param mixed $value a string - object property to be encoded
* @param array $javascriptExpressions
* @param null|string|int $currentKey
* @return mixed
*/
protected static function _recursiveJsonExprFinder(
&$value,
array &$javascriptExpressions,
$currentKey = null
) {
if ($value instanceof Expr) {
// TODO: Optimize with ascii keys, if performance is bad
$magicKey = "____" . $currentKey . "_" . (count($javascriptExpressions));
$javascriptExpressions[] = [
//if currentKey is integer, encodeUnicodeString call is not required.
"magicKey" => (is_int($currentKey)) ? $magicKey : Encoder::encodeUnicodeString($magicKey),
"value" => $value->__toString(),
];
$value = $magicKey;
} elseif (is_array($value)) {
foreach ($value as $k => $v) {
$value[$k] = static::_recursiveJsonExprFinder($value[$k], $javascriptExpressions, $k);
}
} elseif (is_object($value)) {
foreach ($value as $k => $v) {
$value->$k = static::_recursiveJsonExprFinder($value->$k, $javascriptExpressions, $k);
}
}
return $value;
}
/**
* Return the value of an XML attribute text or the text between
* the XML tags
*
* In order to allow Zend\Json\Expr from xml, we check if the node
* matches the pattern that try to detect if it is a new Zend\Json\Expr
* if it matches, we return a new Zend\Json\Expr instead of a text node
*
* @param SimpleXMLElement $simpleXmlElementObject
* @return Expr|string
*/
protected static function _getXmlValue($simpleXmlElementObject)
{
$pattern = '/^[\s]*new Zend[_\\]Json[_\\]Expr[\s]*\([\s]*[\"\']{1}(.*)[\"\']{1}[\s]*\)[\s]*$/';
$matchings = [];
$match = preg_match($pattern, $simpleXmlElementObject, $matchings);
if ($match) {
return new Expr($matchings[1]);
}
return (trim(strval($simpleXmlElementObject)));
}
/**
* _processXml - Contains the logic for xml2json
*
* The logic in this function is a recursive one.
*
* The main caller of this function (i.e. fromXml) needs to provide
* only the first two parameters i.e. the SimpleXMLElement object and
* the flag for ignoring or not ignoring XML attributes. The third parameter
* will be used internally within this function during the recursive calls.
*
* This function converts the SimpleXMLElement object into a PHP array by
* calling a recursive (protected static) function in this class. Once all
* the XML elements are stored in the PHP array, it is returned to the caller.
*
* @param SimpleXMLElement $simpleXmlElementObject
* @param bool $ignoreXmlAttributes
* @param int $recursionDepth
* @throws Exception\RecursionException if the XML tree is deeper than the allowed limit.
* @return array
*/
protected static function _processXml($simpleXmlElementObject, $ignoreXmlAttributes, $recursionDepth = 0)
{
// Keep an eye on how deeply we are involved in recursion.
if ($recursionDepth > static::$maxRecursionDepthAllowed) {
// XML tree is too deep. Exit now by throwing an exception.
throw new RecursionException(
"Function _processXml exceeded the allowed recursion depth of "
. static::$maxRecursionDepthAllowed
);
}
$children = $simpleXmlElementObject->children();
$name = $simpleXmlElementObject->getName();
$value = static::_getXmlValue($simpleXmlElementObject);
$attributes = (array) $simpleXmlElementObject->attributes();
if (!count($children)) {
if (!empty($attributes) && !$ignoreXmlAttributes) {
foreach ($attributes['@attributes'] as $k => $v) {
$attributes['@attributes'][$k] = static::_getXmlValue($v);
}
if (!empty($value)) {
$attributes['@text'] = $value;
}
return [$name => $attributes];
}
return [$name => $value];
}
$childArray = [];
foreach ($children as $child) {
$childname = $child->getName();
$element = static::_processXml($child, $ignoreXmlAttributes, $recursionDepth + 1);
if (array_key_exists($childname, $childArray)) {
if (empty($subChild[$childname])) {
$childArray[$childname] = [$childArray[$childname]];
$subChild[$childname] = true;
}
$childArray[$childname][] = $element[$childname];
} else {
$childArray[$childname] = $element[$childname];
}
}
if (!empty($attributes) && !$ignoreXmlAttributes) {
foreach ($attributes['@attributes'] as $k => $v) {
$attributes['@attributes'][$k] = static::_getXmlValue($v);
}
$childArray['@attributes'] = $attributes['@attributes'];
}
if (!empty($value)) {
$childArray['@text'] = $value;
}
return [$name => $childArray];
}
/**
* @deprecated by https://github.com/zendframework/zf2/pull/6778
* fromXml - Converts XML to JSON
*
* Converts a XML formatted string into a JSON formatted string.
* The value returned will be a string in JSON format.
*
* The caller of this function needs to provide only the first parameter,
* which is an XML formatted String. The second parameter is optional, which
* lets the user to select if the XML attributes in the input XML string
* should be included or ignored in xml2json conversion.
*
* This function converts the XML formatted string into a PHP array by
* calling a recursive (protected static) function in this class. Then, it
* converts that PHP array into JSON by calling the "encode" static function.
*
* NOTE: Encoding native javascript expressions via Zend\Json\Expr is not possible.
*
* @static
* @access public
* @param string $xmlStringContents XML String to be converted
* @param bool $ignoreXmlAttributes Include or exclude XML attributes in
* the xml2json conversion process.
* @return mixed - JSON formatted string on success
* @throws \Zend\Json\Exception\RuntimeException if the input not a XML formatted string
*/
public static function fromXml($xmlStringContents, $ignoreXmlAttributes = true)
{
// Load the XML formatted string into a Simple XML Element object.
$simpleXmlElementObject = XmlSecurity::scan($xmlStringContents);
// If it is not a valid XML content, throw an exception.
if (!$simpleXmlElementObject) {
throw new RuntimeException('Function fromXml was called with an invalid XML formatted string.');
} // End of if ($simpleXmlElementObject === null)
// Call the recursive function to convert the XML into a PHP array.
$resultArray = static::_processXml($simpleXmlElementObject, $ignoreXmlAttributes);
// Convert the PHP array to JSON using Zend\Json\Json encode method.
// It is just that simple.
$jsonStringOutput = static::encode($resultArray);
return($jsonStringOutput);
}
/**
* Pretty-print JSON string
*
* Use 'indent' option to select indentation string - by default it's a tab
*
* @param string $json Original JSON string
* @param array $options Encoding options
* @return string
*/
public static function prettyPrint($json, $options = [])
{
$tokens = preg_split('|([\{\}\]\[,])|', $json, -1, PREG_SPLIT_DELIM_CAPTURE);
$result = "";
$indent = 0;
$ind = " ";
if (isset($options['indent'])) {
$ind = $options['indent'];
}
$inLiteral = false;
foreach ($tokens as $token) {
$token = trim($token);
if ($token == "") {
continue;
}
if (preg_match('/^("(?:.*)"):[ ]?(.*)$/', $token, $matches)) {
$token = $matches[1] . ': ' . $matches[2];
}
$prefix = str_repeat($ind, $indent);
if (!$inLiteral && ($token == "{" || $token == "[")) {
$indent++;
if ($result != "" && $result[strlen($result)-1] == "\n") {
$result .= $prefix;
}
$result .= "$token\n";
} elseif (!$inLiteral && ($token == "}" || $token == "]")) {
$indent--;
$prefix = str_repeat($ind, $indent);
$result .= "\n$prefix$token";
} elseif (!$inLiteral && $token == ",") {
$result .= "$token\n";
} else {
$result .= ($inLiteral ? '' : $prefix) . $token;
//remove escaped backslash sequences causing false positives in next check
$token = str_replace('\\', '', $token);
// Count # of unescaped double-quotes in token, subtract # of
// escaped double-quotes and if the result is odd then we are
// inside a string literal
if ((substr_count($token, '"')-substr_count($token, '\\"')) % 2 != 0) {
$inLiteral = !$inLiteral;
}
}
}
return $result;
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use Zend\Server\Cache as ServerCache;
use Zend\Stdlib\ErrorHandler;
/**
* Zend\Json\Server\Cache: cache Zend\Json\Server\Server server definition and SMD
*/
class Cache extends ServerCache
{
/**
* Cache a service map description (SMD) to a file
*
* Returns true on success, false on failure
*
* @param string $filename
* @param \Zend\Json\Server\Server $server
* @return bool
*/
public static function saveSmd($filename, Server $server)
{
if (!is_string($filename) || (!file_exists($filename) && !is_writable(dirname($filename)))) {
return false;
}
ErrorHandler::start();
$test = file_put_contents($filename, $server->getServiceMap()->toJson());
ErrorHandler::stop();
if (0 === $test) {
return false;
}
return true;
}
/**
* Retrieve a cached SMD
*
* On success, returns the cached SMD (a JSON string); a failure, returns
* boolean false.
*
* @param string $filename
* @return string|false
*/
public static function getSmd($filename)
{
if (!is_string($filename) || !file_exists($filename) || !is_readable($filename)) {
return false;
}
ErrorHandler::start();
$smd = file_get_contents($filename);
ErrorHandler::stop();
if (false === $smd) {
return false;
}
return $smd;
}
/**
* Delete a file containing a cached SMD
*
* @param string $filename
* @return bool
*/
public static function deleteSmd($filename)
{
if (is_string($filename) && file_exists($filename)) {
unlink($filename);
return true;
}
return false;
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use Zend\Http\Client as HttpClient;
use Zend\Server\Client as ServerClient;
class Client implements ServerClient
{
/**
* Full address of the JSON-RPC service.
*
* @var string
*/
protected $serverAddress;
/**
* HTTP Client to use for requests.
*
* @var HttpClient
*/
protected $httpClient;
/**
* Request of the last method call.
*
* @var Request
*/
protected $lastRequest;
/**
* Response received from the last method call.
*
* @var Response
*/
protected $lastResponse;
/**
* Request ID counter.
*
* @var int
*/
protected $id = 0;
/**
* Create a new JSON-RPC client to a remote server.
*
* @param string $server Full address of the JSON-RPC service.
* @param HttpClient $httpClient HTTP Client to use for requests.
*/
public function __construct($server, HttpClient $httpClient = null)
{
$this->httpClient = $httpClient ?: new HttpClient();
$this->serverAddress = $server;
}
/**
* Sets the HTTP client object to use for connecting the JSON-RPC server.
*
* @param HttpClient $httpClient New HTTP client to use.
* @return Client Self instance.
*/
public function setHttpClient(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
return $this;
}
/**
* Gets the HTTP client object.
*
* @return HttpClient HTTP client.
*/
public function getHttpClient()
{
return $this->httpClient;
}
/**
* The request of the last method call.
*
* @return Request Request instance.
*/
public function getLastRequest()
{
return $this->lastRequest;
}
/**
* The response received from the last method call.
*
* @return Response Response instance.
*/
public function getLastResponse()
{
return $this->lastResponse;
}
/**
* Perform a JSON-RPC request and return a response.
*
* @param Request $request Request.
* @return Response Response.
* @throws Exception\HttpException When HTTP communication fails.
*/
public function doRequest($request)
{
$this->lastRequest = $request;
$httpRequest = $this->httpClient->getRequest();
if ($httpRequest->getUriString() === null) {
$this->httpClient->setUri($this->serverAddress);
}
$headers = $httpRequest->getHeaders();
$headers->addHeaders([
'Content-Type' => 'application/json',
'Accept' => 'application/json',
]);
if (!$headers->get('User-Agent')) {
$headers->addHeaderLine('User-Agent', 'Zend_Json_Server_Client');
}
$this->httpClient->setRawBody($request->__toString());
$this->httpClient->setMethod('POST');
$httpResponse = $this->httpClient->send();
if (!$httpResponse->isSuccess()) {
throw new Exception\HttpException(
$httpResponse->getReasonPhrase(),
$httpResponse->getStatusCode()
);
}
$response = new Response();
$this->lastResponse = $response;
// import all response data from JSON HTTP response
$response->loadJson($httpResponse->getBody());
return $response;
}
/**
* Send a JSON-RPC request to the service (for a specific method).
*
* @param string $method Name of the method we want to call.
* @param array $params Array of parameters for the method.
* @return mixed Method call results.
* @throws Exception\ErrorException When remote call fails.
*/
public function call($method, $params = [])
{
$request = $this->createRequest($method, $params);
$response = $this->doRequest($request);
if ($response->isError()) {
$error = $response->getError();
throw new Exception\ErrorException(
$error->getMessage(),
$error->getCode()
);
}
return $response->getResult();
}
/**
* Create request object.
*
* @param string $method Method to call.
* @param array $params List of arguments.
* @return Request Created request.
*/
protected function createRequest($method, array $params)
{
$request = new Request();
$request->setMethod($method)
->setParams($params)
->setId(++$this->id);
return $request;
}
}

View File

@@ -0,0 +1,173 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
class Error
{
const ERROR_PARSE = -32700;
const ERROR_INVALID_REQUEST = -32600;
const ERROR_INVALID_METHOD = -32601;
const ERROR_INVALID_PARAMS = -32602;
const ERROR_INTERNAL = -32603;
const ERROR_OTHER = -32000;
/**
* Current code
* @var int
*/
protected $code = self::ERROR_OTHER;
/**
* Error data
* @var mixed
*/
protected $data;
/**
* Error message
* @var string
*/
protected $message;
/**
* Constructor
*
* @param string $message
* @param int $code
* @param mixed $data
*/
public function __construct($message = null, $code = self::ERROR_OTHER, $data = null)
{
$this->setMessage($message)
->setCode($code)
->setData($data);
}
/**
* Set error code.
*
* If the error code is 0, it will be set to -32000 (ERROR_OTHER).
*
* @param int $code
* @return \Zend\Json\Server\Error
*/
public function setCode($code)
{
if (!is_scalar($code) || is_bool($code) || is_float($code)) {
return $this;
}
if (is_string($code) && !is_numeric($code)) {
return $this;
}
$code = (int) $code;
if (0 === $code) {
$this->code = self::ERROR_OTHER;
} else {
$this->code = $code;
}
return $this;
}
/**
* Get error code
*
* @return int|null
*/
public function getCode()
{
return $this->code;
}
/**
* Set error message
*
* @param string $message
* @return \Zend\Json\Server\Error
*/
public function setMessage($message)
{
if (!is_scalar($message)) {
return $this;
}
$this->message = (string) $message;
return $this;
}
/**
* Get error message
*
* @return string
*/
public function getMessage()
{
return $this->message;
}
/**
* Set error data
*
* @param mixed $data
* @return \Zend\Json\Server\Error
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get error data
*
* @return mixed
*/
public function getData()
{
return $this->data;
}
/**
* Cast error to array
*
* @return array
*/
public function toArray()
{
return [
'code' => $this->getCode(),
'message' => $this->getMessage(),
'data' => $this->getData(),
];
}
/**
* Cast error to JSON
*
* @return string
*/
public function toJson()
{
return \Zend\Json\Json::encode($this->toArray());
}
/**
* Cast to string (JSON)
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Exception;
use Zend\Json\Exception;
/**
* Thrown by Zend\Json\Server\Client when a JSON-RPC fault response is returned.
*/
class ErrorException extends Exception\BadMethodCallException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,16 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Exception;
use Zend\Json\Exception\ExceptionInterface as Exception;
interface ExceptionInterface extends Exception
{
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Exception;
/**
* Thrown by Zend\Json\Server\Client when an HTTP error occurs during an
* JSON-RPC method call.
*/
class HttpException extends RuntimeException
{
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Exception;
use Zend\Json\Exception;
class InvalidArgumentException extends Exception\InvalidArgumentException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,17 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Exception;
use Zend\Json\Exception;
class RuntimeException extends Exception\RuntimeException implements
ExceptionInterface
{
}

View File

@@ -0,0 +1,294 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use Zend\Json;
/**
* @todo Revised method regex to allow NS; however, should SMD be revised to strip PHP NS instead when attaching functions?
*/
class Request
{
/**
* Request ID
* @var mixed
*/
protected $id;
/**
* Flag
* @var bool
*/
protected $isMethodError = false;
/**
* Flag
* @var bool
*/
protected $isParseError = false;
/**
* Requested method
* @var string
*/
protected $method;
/**
* Regex for method
* @var string
*/
protected $methodRegex = '/^[a-z][a-z0-9\\\\_.]*$/i';
/**
* Request parameters
* @var array
*/
protected $params = [];
/**
* JSON-RPC version of request
* @var string
*/
protected $version = '1.0';
/**
* Set request state
*
* @param array $options
* @return \Zend\Json\Server\Request
*/
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
} elseif ($key == 'jsonrpc') {
$this->setVersion($value);
}
}
return $this;
}
/**
* Add a parameter to the request
*
* @param mixed $value
* @param string $key
* @return \Zend\Json\Server\Request
*/
public function addParam($value, $key = null)
{
if ((null === $key) || !is_string($key)) {
$index = count($this->params);
$this->params[$index] = $value;
} else {
$this->params[$key] = $value;
}
return $this;
}
/**
* Add many params
*
* @param array $params
* @return \Zend\Json\Server\Request
*/
public function addParams(array $params)
{
foreach ($params as $key => $value) {
$this->addParam($value, $key);
}
return $this;
}
/**
* Overwrite params
*
* @param array $params
* @return \Zend\Json\Server\Request
*/
public function setParams(array $params)
{
$this->params = [];
return $this->addParams($params);
}
/**
* Retrieve param by index or key
*
* @param int|string $index
* @return mixed|null Null when not found
*/
public function getParam($index)
{
if (array_key_exists($index, $this->params)) {
return $this->params[$index];
}
return;
}
/**
* Retrieve parameters
*
* @return array
*/
public function getParams()
{
return $this->params;
}
/**
* Set request method
*
* @param string $name
* @return \Zend\Json\Server\Request
*/
public function setMethod($name)
{
if (!preg_match($this->methodRegex, $name)) {
$this->isMethodError = true;
} else {
$this->method = $name;
}
return $this;
}
/**
* Get request method name
*
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Was a bad method provided?
*
* @return bool
*/
public function isMethodError()
{
return $this->isMethodError;
}
/**
* Was a malformed JSON provided?
*
* @return bool
*/
public function isParseError()
{
return $this->isParseError;
}
/**
* Set request identifier
*
* @param mixed $name
* @return \Zend\Json\Server\Request
*/
public function setId($name)
{
$this->id = (string) $name;
return $this;
}
/**
* Retrieve request identifier
*
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* Set JSON-RPC version
*
* @param string $version
* @return \Zend\Json\Server\Request
*/
public function setVersion($version)
{
if ('2.0' == $version) {
$this->version = '2.0';
} else {
$this->version = '1.0';
}
return $this;
}
/**
* Retrieve JSON-RPC version
*
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Set request state based on JSON
*
* @param string $json
* @return void
*/
public function loadJson($json)
{
try {
$options = Json\Json::decode($json, Json\Json::TYPE_ARRAY);
$this->setOptions($options);
} catch (\Exception $e) {
$this->isParseError = true;
}
}
/**
* Cast request to JSON
*
* @return string
*/
public function toJson()
{
$jsonArray = [
'method' => $this->getMethod()
];
if (null !== ($id = $this->getId())) {
$jsonArray['id'] = $id;
}
$params = $this->getParams();
if (!empty($params)) {
$jsonArray['params'] = $params;
}
if ('2.0' == $this->getVersion()) {
$jsonArray['jsonrpc'] = '2.0';
}
return Json\Json::encode($jsonArray);
}
/**
* Cast request to string (JSON)
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Request;
use Zend\Json\Server\Request as JsonRequest;
class Http extends JsonRequest
{
/**
* Raw JSON pulled from POST body
* @var string
*/
protected $rawJson;
/**
* Constructor
*
* Pull JSON request from raw POST body and use to populate request.
*
*/
public function __construct()
{
$json = file_get_contents('php://input');
$this->rawJson = $json;
if (!empty($json)) {
$this->loadJson($json);
}
}
/**
* Get JSON from raw POST body
*
* @return string
*/
public function getRawJson()
{
return $this->rawJson;
}
}

View File

@@ -0,0 +1,279 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use Zend\Json\Json;
class Response
{
/**
* Response error
* @var null|Error
*/
protected $error;
/**
* Request ID
* @var mixed
*/
protected $id;
/**
* Result
* @var mixed
*/
protected $result;
/**
* Service map
* @var Smd
*/
protected $serviceMap;
/**
* JSON-RPC version
* @var string
*/
protected $version;
/**
* @var $args
*/
protected $args;
/**
* Set response state
*
* @param array $options
* @return Response
*/
public function setOptions(array $options)
{
// re-produce error state
if (isset($options['error']) && is_array($options['error'])) {
$error = $options['error'];
$options['error'] = new Error($error['message'], $error['code'], $error['data']);
}
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
} elseif ($key == 'jsonrpc') {
$this->setVersion($value);
}
}
return $this;
}
/**
* Set response state based on JSON
*
* @param string $json
* @return void
* @throws Exception\RuntimeException
*/
public function loadJson($json)
{
$options = Json::decode($json, Json::TYPE_ARRAY);
if (!is_array($options)) {
throw new Exception\RuntimeException('json is not a valid response; array expected');
}
$this->setOptions($options);
}
/**
* Set result
*
* @param mixed $value
* @return Response
*/
public function setResult($value)
{
$this->result = $value;
return $this;
}
/**
* Get result
*
* @return mixed
*/
public function getResult()
{
return $this->result;
}
// RPC error, if response results in fault
/**
* Set result error
*
* @param mixed $error
* @return Response
*/
public function setError(Error $error = null)
{
$this->error = $error;
return $this;
}
/**
* Get response error
*
* @return null|Error
*/
public function getError()
{
return $this->error;
}
/**
* Is the response an error?
*
* @return bool
*/
public function isError()
{
return $this->getError() instanceof Error;
}
/**
* Set request ID
*
* @param mixed $name
* @return Response
*/
public function setId($name)
{
$this->id = $name;
return $this;
}
/**
* Get request ID
*
* @return mixed
*/
public function getId()
{
return $this->id;
}
/**
* Set JSON-RPC version
*
* @param string $version
* @return Response
*/
public function setVersion($version)
{
$version = (string) $version;
if ('2.0' == $version) {
$this->version = '2.0';
} else {
$this->version = null;
}
return $this;
}
/**
* Retrieve JSON-RPC version
*
* @return string
*/
public function getVersion()
{
return $this->version;
}
/**
* Cast to JSON
*
* @return string
*/
public function toJson()
{
if ($this->isError()) {
$response = [
'error' => $this->getError()->toArray(),
'id' => $this->getId(),
];
} else {
$response = [
'result' => $this->getResult(),
'id' => $this->getId(),
];
}
if (null !== ($version = $this->getVersion())) {
$response['jsonrpc'] = $version;
}
return \Zend\Json\Json::encode($response);
}
/**
* Retrieve args
*
* @return mixed
*/
public function getArgs()
{
return $this->args;
}
/**
* Set args
*
* @param mixed $args
* @return self
*/
public function setArgs($args)
{
$this->args = $args;
return $this;
}
/**
* Set service map object
*
* @param Smd $serviceMap
* @return Response
*/
public function setServiceMap($serviceMap)
{
$this->serviceMap = $serviceMap;
return $this;
}
/**
* Retrieve service map
*
* @return Smd|null
*/
public function getServiceMap()
{
return $this->serviceMap;
}
/**
* Cast to string (JSON)
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
}

View File

@@ -0,0 +1,62 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Response;
use Zend\Json\Server\Response as JsonResponse;
class Http extends JsonResponse
{
/**
* Emit JSON
*
* Send appropriate HTTP headers. If no Id, then return an empty string.
*
* @return string
*/
public function toJson()
{
$this->sendHeaders();
if (!$this->isError() && null === $this->getId()) {
return '';
}
return parent::toJson();
}
/**
* Send headers
*
* If headers are already sent, do nothing. If null ID, send HTTP 204
* header. Otherwise, send content type header based on content type of
* service map.
*
* @return void
*/
public function sendHeaders()
{
if (headers_sent()) {
return;
}
if (!$this->isError() && (null === $this->getId())) {
header('HTTP/1.1 204 No Content');
return;
}
if (null === ($smd = $this->getServiceMap())) {
return;
}
$contentType = $smd->getContentType();
if (!empty($contentType)) {
header('Content-Type: ' . $contentType);
}
}
}

View File

@@ -0,0 +1,564 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use ReflectionFunction;
use ReflectionMethod;
use Zend\Server\AbstractServer;
use Zend\Server\Definition;
use Zend\Server\Method;
use Zend\Server\Reflection;
class Server extends AbstractServer
{
/**#@+
* Version Constants
*/
const VERSION_1 = '1.0';
const VERSION_2 = '2.0';
/**#@-*/
/**
* Flag: whether or not to auto-emit the response
* @var bool
*/
protected $returnResponse = false;
/**
* Inherited from Zend\Server\AbstractServer
*
* @var bool Flag; allow overwriting existing methods when creating server definition
*/
protected $overwriteExistingMethods = true;
/**
* Request object
* @var Request
*/
protected $request;
/**
* Response object
* @var Response
*/
protected $response;
/**
* SMD object
* @var Smd
*/
protected $serviceMap;
/**
* SMD class accessors
* @var array
*/
protected $smdMethods;
/**
* Attach a function or callback to the server
*
* @param string|array|callable $function Valid PHP callback
* @param string $namespace Ignored
* @throws Exception\InvalidArgumentException if function invalid or not callable
* @return Server
*/
public function addFunction($function, $namespace = '')
{
if (!is_string($function) && (!is_array($function) || (2 > count($function)))) {
throw new Exception\InvalidArgumentException('Unable to attach function; invalid');
}
if (!is_callable($function)) {
throw new Exception\InvalidArgumentException('Unable to attach function; does not exist');
}
$argv = null;
if (2 < func_num_args()) {
$argv = func_get_args();
$argv = array_slice($argv, 2);
}
$class = null;
if (is_string($function)) {
$method = Reflection::reflectFunction($function, $argv, $namespace);
} else {
$class = array_shift($function);
$action = array_shift($function);
$reflection = Reflection::reflectClass($class, $argv, $namespace);
$methods = $reflection->getMethods();
$found = false;
foreach ($methods as $method) {
if ($action == $method->getName()) {
$found = true;
break;
}
}
if (!$found) {
$this->fault('Method not found', Error::ERROR_INVALID_METHOD);
return $this;
}
}
$definition = $this->_buildSignature($method, $class);
$this->_addMethodServiceMap($definition);
return $this;
}
/**
* Register a class with the server
*
* @param string $class
* @param string $namespace Ignored
* @param mixed $argv Ignored
* @return Server
*/
public function setClass($class, $namespace = '', $argv = null)
{
if (2 < func_num_args()) {
$argv = func_get_args();
$argv = array_slice($argv, 2);
}
$reflection = Reflection::reflectClass($class, $argv, $namespace);
foreach ($reflection->getMethods() as $method) {
$definition = $this->_buildSignature($method, $class);
$this->_addMethodServiceMap($definition);
}
return $this;
}
/**
* Indicate fault response
*
* @param string $fault
* @param int $code
* @param mixed $data
* @return Error
*/
public function fault($fault = null, $code = 404, $data = null)
{
$error = new Error($fault, $code, $data);
$this->getResponse()->setError($error);
return $error;
}
/**
* Handle request
*
* @param Request $request
* @return null|Response
* @throws Exception\InvalidArgumentException
*/
public function handle($request = false)
{
if ((false !== $request) && (!$request instanceof Request)) {
throw new Exception\InvalidArgumentException('Invalid request type provided; cannot handle');
} elseif ($request) {
$this->setRequest($request);
}
// Handle request
$this->_handle();
// Get response
$response = $this->_getReadyResponse();
// Emit response?
if (!$this->returnResponse) {
echo $response;
return;
}
// or return it?
return $response;
}
/**
* Load function definitions
*
* @param array|Definition $definition
* @throws Exception\InvalidArgumentException
* @return void
*/
public function loadFunctions($definition)
{
if (!is_array($definition) && (!$definition instanceof Definition)) {
throw new Exception\InvalidArgumentException('Invalid definition provided to loadFunctions()');
}
foreach ($definition as $key => $method) {
$this->table->addMethod($method, $key);
$this->_addMethodServiceMap($method);
}
}
public function setPersistence($mode)
{
}
/**
* Set request object
*
* @param Request $request
* @return Server
*/
public function setRequest(Request $request)
{
$this->request = $request;
return $this;
}
/**
* Get JSON-RPC request object
*
* @return Request
*/
public function getRequest()
{
if (null === ($request = $this->request)) {
$this->setRequest(new Request\Http());
}
return $this->request;
}
/**
* Set response object
*
* @param Response $response
* @return Server
*/
public function setResponse(Response $response)
{
$this->response = $response;
return $this;
}
/**
* Get response object
*
* @return Response
*/
public function getResponse()
{
if (null === ($response = $this->response)) {
$this->setResponse(new Response\Http());
}
return $this->response;
}
/**
* Set return response flag
*
* If true, {@link handle()} will return the response instead of
* automatically sending it back to the requesting client.
*
* The response is always available via {@link getResponse()}.
*
* @param bool $flag
* @return Server
*/
public function setReturnResponse($flag = true)
{
$this->returnResponse = (bool) $flag;
return $this;
}
/**
* Retrieve return response flag
*
* @return bool
*/
public function getReturnResponse()
{
return $this->returnResponse;
}
// overloading for SMD metadata
/**
* Overload to accessors of SMD object
*
* @param string $method
* @param array $args
* @return mixed
*/
public function __call($method, $args)
{
if (preg_match('/^(set|get)/', $method, $matches)) {
if (in_array($method, $this->_getSmdMethods())) {
if ('set' == $matches[1]) {
$value = array_shift($args);
$this->getServiceMap()->$method($value);
return $this;
} else {
return $this->getServiceMap()->$method();
}
}
}
return;
}
/**
* Retrieve SMD object
*
* @return Smd
*/
public function getServiceMap()
{
if (null === $this->serviceMap) {
$this->serviceMap = new Smd();
}
return $this->serviceMap;
}
/**
* Add service method to service map
*
* @param Method\Definition $method
* @return void
*/
protected function _addMethodServiceMap(Method\Definition $method)
{
$serviceInfo = [
'name' => $method->getName(),
'return' => $this->_getReturnType($method),
];
$params = $this->_getParams($method);
$serviceInfo['params'] = $params;
$serviceMap = $this->getServiceMap();
if (false !== $serviceMap->getService($serviceInfo['name'])) {
$serviceMap->removeService($serviceInfo['name']);
}
$serviceMap->addService($serviceInfo);
}
/**
* Translate PHP type to JSON type
*
* @param string $type
* @return string
*/
protected function _fixType($type)
{
return $type;
}
/**
* Get default params from signature
*
* @param array $args
* @param array $params
* @return array
*/
protected function _getDefaultParams(array $args, array $params)
{
if (false === $this->isAssociative($args)) {
$params = array_slice($params, count($args));
}
foreach ($params as $param) {
if (isset($args[$param['name']]) || !array_key_exists('default', $param)) {
continue;
}
$args[$param['name']] = $param['default'];
}
return $args;
}
/**
* check whether array is associative or not
*
* @param array $array
* @return bool
*/
private function isAssociative(array $array)
{
$keys = array_keys($array);
return ($keys != array_keys($keys));
}
/**
* Get method param type
*
* @param Method\Definition $method
* @return string|array
*/
protected function _getParams(Method\Definition $method)
{
$params = [];
foreach ($method->getPrototypes() as $prototype) {
foreach ($prototype->getParameterObjects() as $key => $parameter) {
if (!isset($params[$key])) {
$params[$key] = [
'type' => $parameter->getType(),
'name' => $parameter->getName(),
'optional' => $parameter->isOptional(),
];
if (null !== ($default = $parameter->getDefaultValue())) {
$params[$key]['default'] = $default;
}
$description = $parameter->getDescription();
if (!empty($description)) {
$params[$key]['description'] = $description;
}
continue;
}
$newType = $parameter->getType();
if (!is_array($params[$key]['type'])) {
if ($params[$key]['type'] == $newType) {
continue;
}
$params[$key]['type'] = (array) $params[$key]['type'];
} elseif (in_array($newType, $params[$key]['type'])) {
continue;
}
array_push($params[$key]['type'], $parameter->getType());
}
}
return $params;
}
/**
* Set response state
*
* @return Response
*/
protected function _getReadyResponse()
{
$request = $this->getRequest();
$response = $this->getResponse();
$response->setServiceMap($this->getServiceMap());
if (null !== ($id = $request->getId())) {
$response->setId($id);
}
if (null !== ($version = $request->getVersion())) {
$response->setVersion($version);
}
return $response;
}
/**
* Get method return type
*
* @param Method\Definition $method
* @return string|array
*/
protected function _getReturnType(Method\Definition $method)
{
$return = [];
foreach ($method->getPrototypes() as $prototype) {
$return[] = $prototype->getReturnType();
}
if (1 == count($return)) {
return $return[0];
}
return $return;
}
/**
* Retrieve list of allowed SMD methods for proxying
*
* @return array
*/
protected function _getSmdMethods()
{
if (null === $this->smdMethods) {
$this->smdMethods = [];
$methods = get_class_methods('Zend\\Json\\Server\\Smd');
foreach ($methods as $method) {
if (!preg_match('/^(set|get)/', $method)) {
continue;
}
if (strstr($method, 'Service')) {
continue;
}
$this->smdMethods[] = $method;
}
}
return $this->smdMethods;
}
/**
* Internal method for handling request
*
* @return void
*/
protected function _handle()
{
$request = $this->getRequest();
if ($request->isParseError()) {
return $this->fault('Parse error', Error::ERROR_PARSE);
}
if (!$request->isMethodError() && (null === $request->getMethod())) {
return $this->fault('Invalid Request', Error::ERROR_INVALID_REQUEST);
}
if ($request->isMethodError()) {
return $this->fault('Invalid Request', Error::ERROR_INVALID_REQUEST);
}
$method = $request->getMethod();
if (!$this->table->hasMethod($method)) {
return $this->fault('Method not found', Error::ERROR_INVALID_METHOD);
}
$params = $request->getParams();
$invokable = $this->table->getMethod($method);
$serviceMap = $this->getServiceMap();
$service = $serviceMap->getService($method);
$serviceParams = $service->getParams();
if (count($params) < count($serviceParams)) {
$params = $this->_getDefaultParams($params, $serviceParams);
}
//Make sure named parameters are passed in correct order
if (is_string(key($params))) {
$callback = $invokable->getCallback();
if ('function' == $callback->getType()) {
$reflection = new ReflectionFunction($callback->getFunction());
} else {
$reflection = new ReflectionMethod(
$callback->getClass(),
$callback->getMethod()
);
}
$orderedParams = [];
foreach ($reflection->getParameters() as $refParam) {
if (array_key_exists($refParam->getName(), $params)) {
$orderedParams[$refParam->getName()] = $params[$refParam->getName()];
} elseif ($refParam->isOptional()) {
$orderedParams[$refParam->getName()] = null;
} else {
return $this->fault('Invalid params', Error::ERROR_INVALID_PARAMS);
}
}
$params = $orderedParams;
}
try {
$result = $this->_dispatch($invokable, $params);
} catch (\Exception $e) {
return $this->fault($e->getMessage(), $e->getCode(), $e);
}
$this->getResponse()->setResult($result);
}
}

View File

@@ -0,0 +1,461 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server;
use Zend\Json\Server\Exception\InvalidArgumentException;
use Zend\Json\Server\Exception\RuntimeException;
class Smd
{
const ENV_JSONRPC_1 = 'JSON-RPC-1.0';
const ENV_JSONRPC_2 = 'JSON-RPC-2.0';
const SMD_VERSION = '2.0';
/**
* Content type
* @var string
*/
protected $contentType = 'application/json';
/**
* Content type regex
* @var string
*/
protected $contentTypeRegex = '#[a-z]+/[a-z][a-z-]+#i';
/**
* Service description
* @var string
*/
protected $description;
/**
* Generate Dojo-compatible SMD
* @var bool
*/
protected $dojoCompatible = false;
/**
* Current envelope
* @var string
*/
protected $envelope = self::ENV_JSONRPC_1;
/**
* Allowed envelope types
* @var array
*/
protected $envelopeTypes = [
self::ENV_JSONRPC_1,
self::ENV_JSONRPC_2,
];
/**
* Service id
* @var string
*/
protected $id;
/**
* Services offered
* @var array
*/
protected $services = [];
/**
* Service target
* @var string
*/
protected $target;
/**
* Global transport
* @var string
*/
protected $transport = 'POST';
/**
* Allowed transport types
* @var array
*/
protected $transportTypes = ['POST'];
/**
* Set object state via options
*
* @param array $options
* @return Smd
*/
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
$method = 'set' . ucfirst($key);
if (method_exists($this, $method)) {
$this->$method($value);
}
}
return $this;
}
/**
* Set transport
*
* @param string $transport
* @throws Exception\InvalidArgumentException
* @return \Zend\Json\Server\Smd
*/
public function setTransport($transport)
{
if (!in_array($transport, $this->transportTypes)) {
throw new InvalidArgumentException("Invalid transport '{$transport}' specified");
}
$this->transport = $transport;
return $this;
}
/**
* Get transport
*
* @return string
*/
public function getTransport()
{
return $this->transport;
}
/**
* Set envelope
*
* @param string $envelopeType
* @throws Exception\InvalidArgumentException
* @return Smd
*/
public function setEnvelope($envelopeType)
{
if (!in_array($envelopeType, $this->envelopeTypes)) {
throw new InvalidArgumentException("Invalid envelope type '{$envelopeType}'");
}
$this->envelope = $envelopeType;
return $this;
}
/**
* Retrieve envelope
*
* @return string
*/
public function getEnvelope()
{
return $this->envelope;
}
// Content-Type of response; default to application/json
/**
* Set content type
*
* @param string $type
* @throws Exception\InvalidArgumentException
* @return \Zend\Json\Server\Smd
*/
public function setContentType($type)
{
if (!preg_match($this->contentTypeRegex, $type)) {
throw new InvalidArgumentException("Invalid content type '{$type}' specified");
}
$this->contentType = $type;
return $this;
}
/**
* Retrieve content type
*
* @return string
*/
public function getContentType()
{
return $this->contentType;
}
/**
* Set service target
*
* @param string $target
* @return Smd
*/
public function setTarget($target)
{
$this->target = (string) $target;
return $this;
}
/**
* Retrieve service target
*
* @return string
*/
public function getTarget()
{
return $this->target;
}
/**
* Set service ID
*
* @param string $id
* @return Smd
*/
public function setId($id)
{
$this->id = (string) $id;
return $this->id;
}
/**
* Get service id
*
* @return string
*/
public function getId()
{
return $this->id;
}
/**
* Set service description
*
* @param string $description
* @return Smd
*/
public function setDescription($description)
{
$this->description = (string) $description;
return $this->description;
}
/**
* Get service description
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Indicate whether or not to generate Dojo-compatible SMD
*
* @param bool $flag
* @return Smd
*/
public function setDojoCompatible($flag)
{
$this->dojoCompatible = (bool) $flag;
return $this;
}
/**
* Is this a Dojo compatible SMD?
*
* @return bool
*/
public function isDojoCompatible()
{
return $this->dojoCompatible;
}
/**
* Add Service
*
* @param Smd\Service|array $service
* @throws Exception\RuntimeException
* @throws Exception\InvalidArgumentException
* @return Smd
*/
public function addService($service)
{
if ($service instanceof Smd\Service) {
$name = $service->getName();
} elseif (is_array($service)) {
$service = new Smd\Service($service);
$name = $service->getName();
} else {
throw new InvalidArgumentException('Invalid service passed to addService()');
}
if (array_key_exists($name, $this->services)) {
throw new RuntimeException('Attempt to register a service already registered detected');
}
$this->services[$name] = $service;
return $this;
}
/**
* Add many services
*
* @param array $services
* @return Smd
*/
public function addServices(array $services)
{
foreach ($services as $service) {
$this->addService($service);
}
return $this;
}
/**
* Overwrite existing services with new ones
*
* @param array $services
* @return Smd
*/
public function setServices(array $services)
{
$this->services = [];
return $this->addServices($services);
}
/**
* Get service object
*
* @param string $name
* @return bool|Smd\Service
*/
public function getService($name)
{
if (array_key_exists($name, $this->services)) {
return $this->services[$name];
}
return false;
}
/**
* Return services
*
* @return array
*/
public function getServices()
{
return $this->services;
}
/**
* Remove service
*
* @param string $name
* @return bool
*/
public function removeService($name)
{
if (array_key_exists($name, $this->services)) {
unset($this->services[$name]);
return true;
}
return false;
}
/**
* Cast to array
*
* @return array
*/
public function toArray()
{
if ($this->isDojoCompatible()) {
return $this->toDojoArray();
}
$description = $this->getDescription();
$transport = $this->getTransport();
$envelope = $this->getEnvelope();
$contentType = $this->getContentType();
$SMDVersion = static::SMD_VERSION;
$service = compact('transport', 'envelope', 'contentType', 'SMDVersion', 'description');
if (null !== ($target = $this->getTarget())) {
$service['target'] = $target;
}
if (null !== ($id = $this->getId())) {
$service['id'] = $id;
}
$services = $this->getServices();
if (!empty($services)) {
$service['services'] = [];
foreach ($services as $name => $svc) {
$svc->setEnvelope($envelope);
$service['services'][$name] = $svc->toArray();
}
$service['methods'] = $service['services'];
}
return $service;
}
/**
* Export to DOJO-compatible SMD array
*
* @return array
*/
public function toDojoArray()
{
$SMDVersion = '.1';
$serviceType = 'JSON-RPC';
$service = compact('SMDVersion', 'serviceType');
$target = $this->getTarget();
$services = $this->getServices();
if (!empty($services)) {
$service['methods'] = [];
foreach ($services as $name => $svc) {
$method = [
'name' => $name,
'serviceURL' => $target,
];
$params = [];
foreach ($svc->getParams() as $param) {
$paramName = array_key_exists('name', $param) ? $param['name'] : $param['type'];
$params[] = [
'name' => $paramName,
'type' => $param['type'],
];
}
if (!empty($params)) {
$method['parameters'] = $params;
}
$service['methods'][] = $method;
}
}
return $service;
}
/**
* Cast to JSON
*
* @return string
*/
public function toJson()
{
return \Zend\Json\Json::encode($this->toArray());
}
/**
* Cast to string (JSON)
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
}

View File

@@ -0,0 +1,465 @@
<?php
/**
* Zend Framework (http://framework.zend.com/)
*
* @link http://github.com/zendframework/zf2 for the canonical source repository
* @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
namespace Zend\Json\Server\Smd;
use Zend\Json\Server\Exception\InvalidArgumentException;
use Zend\Json\Server\Smd;
/**
* Create Service Mapping Description for a method
*
* @todo Revised method regex to allow NS; however, should SMD be revised to strip PHP NS instead when attaching functions?
*/
class Service
{
/**#@+
* Service metadata
* @var string
*/
protected $envelope = Smd::ENV_JSONRPC_1;
protected $name;
protected $return;
protected $target;
protected $transport = 'POST';
/**#@-*/
/**
* Allowed envelope types
* @var array
*/
protected $envelopeTypes = [
Smd::ENV_JSONRPC_1,
Smd::ENV_JSONRPC_2,
];
/**
* Regex for names
* @var string
*
* @link http://php.net/manual/en/language.oop5.basic.php
* @link http://www.jsonrpc.org/specification#request_object
*/
protected $nameRegex = '/^(?!^rpc\.)[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff\.\\\]*$/';
/**
* Parameter option types
* @var array
*/
protected $paramOptionTypes = [
'name' => 'is_string',
'optional' => 'is_bool',
'default' => null,
'description' => 'is_string',
];
/**
* Service params
* @var array
*/
protected $params = [];
/**
* Mapping of parameter types to JSON-RPC types
* @var array
*/
protected $paramMap = [
'any' => 'any',
'arr' => 'array',
'array' => 'array',
'assoc' => 'object',
'bool' => 'boolean',
'boolean' => 'boolean',
'dbl' => 'float',
'double' => 'float',
'false' => 'boolean',
'float' => 'float',
'hash' => 'object',
'integer' => 'integer',
'int' => 'integer',
'mixed' => 'any',
'nil' => 'null',
'null' => 'null',
'object' => 'object',
'string' => 'string',
'str' => 'string',
'struct' => 'object',
'true' => 'boolean',
'void' => 'null',
];
/**
* Allowed transport types
* @var array
*/
protected $transportTypes = [
'POST',
];
/**
* Constructor
*
* @param string|array $spec
* @throws InvalidArgumentException if no name provided
*/
public function __construct($spec)
{
if (is_string($spec)) {
$this->setName($spec);
} elseif (is_array($spec)) {
$this->setOptions($spec);
}
if (null == $this->getName()) {
throw new InvalidArgumentException('SMD service description requires a name; none provided');
}
}
/**
* Set object state
*
* @param array $options
* @return Service
*/
public function setOptions(array $options)
{
$methods = get_class_methods($this);
foreach ($options as $key => $value) {
if ('options' == strtolower($key)) {
continue;
}
$method = 'set' . ucfirst($key);
if (in_array($method, $methods)) {
$this->$method($value);
}
}
return $this;
}
/**
* Set service name
*
* @param string $name
* @return Service
* @throws InvalidArgumentException
*/
public function setName($name)
{
$name = (string) $name;
if (!preg_match($this->nameRegex, $name)) {
throw new InvalidArgumentException("Invalid name '{$name} provided for service; must follow PHP method naming conventions");
}
$this->name = $name;
return $this;
}
/**
* Retrieve name
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Set Transport
*
* Currently limited to POST
*
* @param string $transport
* @throws InvalidArgumentException
* @return Service
*/
public function setTransport($transport)
{
if (!in_array($transport, $this->transportTypes)) {
throw new InvalidArgumentException("Invalid transport '{$transport}'; please select one of (" . implode(', ', $this->transportTypes) . ')');
}
$this->transport = $transport;
return $this;
}
/**
* Get transport
*
* @return string
*/
public function getTransport()
{
return $this->transport;
}
/**
* Set service target
*
* @param string $target
* @return Service
*/
public function setTarget($target)
{
$this->target = (string) $target;
return $this;
}
/**
* Get service target
*
* @return string
*/
public function getTarget()
{
return $this->target;
}
/**
* Set envelope type
*
* @param string $envelopeType
* @throws InvalidArgumentException
* @return Service
*/
public function setEnvelope($envelopeType)
{
if (!in_array($envelopeType, $this->envelopeTypes)) {
throw new InvalidArgumentException("Invalid envelope type '{$envelopeType}'; please specify one of (" . implode(', ', $this->envelopeTypes) . ')');
}
$this->envelope = $envelopeType;
return $this;
}
/**
* Get envelope type
*
* @return string
*/
public function getEnvelope()
{
return $this->envelope;
}
/**
* Add a parameter to the service
*
* @param string|array $type
* @param array $options
* @param int|null $order
* @throws InvalidArgumentException
* @return Service
*/
public function addParam($type, array $options = [], $order = null)
{
if (is_string($type)) {
$type = $this->_validateParamType($type);
} elseif (is_array($type)) {
foreach ($type as $key => $paramType) {
$type[$key] = $this->_validateParamType($paramType);
}
} else {
throw new InvalidArgumentException('Invalid param type provided');
}
$paramOptions = [
'type' => $type,
];
foreach ($options as $key => $value) {
if (in_array($key, array_keys($this->paramOptionTypes))) {
if (null !== ($callback = $this->paramOptionTypes[$key])) {
if (!$callback($value)) {
continue;
}
}
$paramOptions[$key] = $value;
}
}
$this->params[] = [
'param' => $paramOptions,
'order' => $order,
];
return $this;
}
/**
* Add params
*
* Each param should be an array, and should include the key 'type'.
*
* @param array $params
* @return Service
*/
public function addParams(array $params)
{
ksort($params);
foreach ($params as $options) {
if (!is_array($options)) {
continue;
}
if (!array_key_exists('type', $options)) {
continue;
}
$type = $options['type'];
$order = (array_key_exists('order', $options)) ? $options['order'] : null;
$this->addParam($type, $options, $order);
}
return $this;
}
/**
* Overwrite all parameters
*
* @param array $params
* @return Service
*/
public function setParams(array $params)
{
$this->params = [];
return $this->addParams($params);
}
/**
* Get all parameters
*
* Returns all params in specified order.
*
* @return array
*/
public function getParams()
{
$params = [];
$index = 0;
foreach ($this->params as $param) {
if (null === $param['order']) {
if (array_search($index, array_keys($params), true)) {
++$index;
}
$params[$index] = $param['param'];
++$index;
} else {
$params[$param['order']] = $param['param'];
}
}
ksort($params);
return $params;
}
/**
* Set return type
*
* @param string|array $type
* @throws InvalidArgumentException
* @return Service
*/
public function setReturn($type)
{
if (is_string($type)) {
$type = $this->_validateParamType($type, true);
} elseif (is_array($type)) {
foreach ($type as $key => $returnType) {
$type[$key] = $this->_validateParamType($returnType, true);
}
} else {
throw new InvalidArgumentException("Invalid param type provided ('" . gettype($type) . "')");
}
$this->return = $type;
return $this;
}
/**
* Get return type
*
* @return string|array
*/
public function getReturn()
{
return $this->return;
}
/**
* Cast service description to array
*
* @return array
*/
public function toArray()
{
$envelope = $this->getEnvelope();
$target = $this->getTarget();
$transport = $this->getTransport();
$parameters = $this->getParams();
$returns = $this->getReturn();
$name = $this->getName();
if (empty($target)) {
return compact('envelope', 'transport', 'name', 'parameters', 'returns');
}
return compact('envelope', 'target', 'transport', 'name', 'parameters', 'returns');
}
/**
* Return JSON encoding of service
*
* @return string
*/
public function toJson()
{
$service = [$this->getName() => $this->toArray()];
return \Zend\Json\Json::encode($service);
}
/**
* Cast to string
*
* @return string
*/
public function __toString()
{
return $this->toJson();
}
/**
* Validate parameter type
*
* @param string $type
* @param bool $isReturn
* @return string
* @throws InvalidArgumentException
*/
protected function _validateParamType($type, $isReturn = false)
{
if (!is_string($type)) {
throw new InvalidArgumentException("Invalid param type provided ('{$type}')");
}
if (!array_key_exists($type, $this->paramMap)) {
$type = 'object';
}
$paramType = $this->paramMap[$type];
if (!$isReturn && ('null' == $paramType)) {
throw new InvalidArgumentException("Invalid param type provided ('{$type}')");
}
return $paramType;
}
}