update v1.0.7.9 R.C.

This is a Release Candidate. We are still testing.
This commit is contained in:
Sujit Prasad
2016-08-03 20:04:36 +05:30
parent 8b6b924d09
commit ffa56a43cb
3830 changed files with 181529 additions and 495353 deletions

View File

@@ -0,0 +1,62 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 2.6.1 - 2016-02-04
### Added
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- [#18](https://github.com/zendframework/zend-json/pull/18) updates dependencies
to allow usage on PHP 7, as well as with zend-stdlib v3.
## 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,43 @@
{
"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",
"extra": {
"branch-alias": {
"dev-master": "2.6-dev",
"dev-develop": "2.7-dev"
}
},
"require": {
"php": "^5.5 || ^7.0"
},
"require-dev": {
"zendframework/zend-http": "^2.5.4",
"zendframework/zend-server": "^2.6.1",
"zendframework/zend-stdlib": "^2.5 || ^3.0",
"zendframework/zendxml": "^1.0.2",
"fabpot/php-cs-fixer": "1.7.*",
"phpunit/PHPUnit": "~4.0"
},
"suggest": {
"zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server",
"zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server",
"zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses",
"zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage"
},
"autoload": {
"psr-4": {
"Zend\\Json\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ZendTest\\Json\\": "test/"
}
}
}

View File

@@ -0,0 +1,29 @@
# Basic Usage
Usage of `Zend\Json` involves using the two public static methods available:
`Zend\Json\Json::encode()` and `Zend\Json\Json::decode()`.
```php
// Retrieve a value:
$phpNative = Zend\Json\Json::decode($encodedValue);
// Encode it to return to the client:
$json = Zend\Json\Json::encode($phpNative);
```
## Pretty-printing JSON
Sometimes, it may be hard to explore *JSON* data generated by `Zend\Json\Json::encode()`, since it
has no spacing or indentation. In order to make it easier, `Zend\Json\Json` allows you to
pretty-print *JSON* data in the human-readable format with `Zend\Json\Json::prettyPrint()`.
```php
// Encode it to return to the client:
$json = Zend\Json\Json::encode($phpNative);
if ($debug) {
echo Zend\Json\Json::prettyPrint($json, array("indent" => " "));
}
```
Second optional argument of `Zend\Json\Json::prettyPrint()` is an option array. Option `indent`
allows to set indentation string - by default it's a single tab character.

View File

@@ -0,0 +1,16 @@
# Introduction
`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/).
*JSON*, JavaScript Object Notation, can be used for data interchange between JavaScript and other
languages. Since *JSON* can be directly evaluated by JavaScript, it is a more efficient and
lightweight format than *XML* for exchanging data with JavaScript clients.
In addition, `Zend\Json` provides a useful way to convert any arbitrary *XML* formatted string into
a *JSON* formatted string. This built-in feature will enable *PHP* developers to transform the
enterprise data encoded in *XML* format into *JSON* format before sending it to browser-based Ajax
client applications. It provides an easy way to do dynamic data conversion on the server-side code
thereby avoiding unnecessary *XML* parsing in the browser-side applications. It offers a nice
utility function that results in easier application-specific data processing techniques.

View File

@@ -0,0 +1,101 @@
# Advanced Usage
## JSON Objects
When encoding *PHP* objects as *JSON*, all public properties of that object will be encoded in a
*JSON* object.
*JSON* does not allow object references, so care should be taken not to encode objects with
recursive references. If you have issues with recursion, `Zend\Json\Json::encode()` and
`Zend\Json\Encoder::encode()` allow an optional second parameter to check for recursion; if an
object is serialized twice, an exception will be thrown.
Decoding *JSON* objects poses an additional difficulty, however, since JavaScript objects correspond
most closely to *PHP*'s associative array. Some suggest that a class identifier should be passed,
and an object instance of that class should be created and populated with the key/value pairs of the
*JSON* object; others feel this could pose a substantial security risk.
By default, `Zend\Json\Json` will decode *JSON* objects as `stdClass` objects. However, if you
desire an associative array returned, you can specify this:
```php
// Decode JSON objects as PHP array
$phpNative = Zend\Json\Json::decode($encodedValue, Zend\Json\Json::TYPE_ARRAY);
```
Any objects thus decoded are returned as associative arrays with keys and values corresponding to
the key/value pairs in the *JSON* notation.
The recommendation of Zend Framework is that the individual developer should decide how to decode
*JSON* objects. If an object of a specified type should be created, it can be created in the
developer code and populated with the values decoded using `Zend\Json`.
## Encoding PHP objects
If you are encoding *PHP* objects by default the encoding mechanism can only access public
properties of these objects. When a method `toJson()` is implemented on an object to encode,
`Zend\Json\Json` calls this method and expects the object to return a *JSON* representation of its
internal state.
`Zend\Json\Json` can encode PHP objects recursively but does not do so by default. This can be
enabled by passing `true` as a second argument to `Zend\Json\Json::encode()`.
```php
// Encode PHP object recursively
$jsonObject = Zend\Json\Json::encode($data, true);
```
When doing recursive encoding of objects, as JSON does not support cycles, an
`Zend\Json\Exception\RecursionException` will be thrown. If you wish, you can silence these
exceptions by passing the `silenceCyclicalExceptions` option:
```php
$jsonObject = Zend\Json\Json::encode(
$data,
true,
array('silenceCyclicalExceptions' => true)
);
```
## Internal Encoder/Decoder
`Zend\Json` has two different modes depending if ext/json is enabled in your *PHP* installation or
not. If ext/json is installed by default `json_encode()` and `json_decode()` functions are used for
encoding and decoding *JSON*. If ext/json is not installed a Zend Framework implementation in *PHP*
code is used for en-/decoding. This is considerably slower than using the *PHP* extension, but
behaves exactly the same.
Still sometimes you might want to use the internal encoder/decoder even if you have ext/json
installed. You can achieve this by calling:
```php
Zend\Json\Json::$useBuiltinEncoderDecoder = true;
```
## JSON Expressions
JavaScript makes heavy use of anonymous function callbacks, which can be saved within *JSON* object
variables. Still they only work if not returned inside double quotes, which `Zend\Json` naturally
does. With the Expression support for `Zend\Json` support you can encode *JSON* objects with valid
JavaScript callbacks. This works for both `json_encode()` or the internal encoder.
A JavaScript callback is represented using the `Zend\Json\Expr` object. It implements the value
object pattern and is immutable. You can set the JavaScript expression as the first constructor
argument. By default `Zend\Json\Json::encode` does not encode JavaScript callbacks, you have to pass
the option `enableJsonExprFinder` and set it to `TRUE` into the `encode` function. If enabled the
expression support works for all nested expressions in large object structures. A usage example
would look like:
```php
$data = array(
'onClick' => new Zend\Json\Expr('function() {'
. 'alert("I am a valid JavaScript callback '
. 'created by Zend\Json"); }'),
'other' => 'no expression',
);
$jsonObjectWithExpression = Zend\Json\Json::encode(
$data,
false,
array('enableJsonExprFinder' => true)
);
```

View File

@@ -0,0 +1,368 @@
# Zend\\Json\\Server - JSON-RPC server
## Introduction
`Zend\Json\Server` is a [JSON-RPC](http://groups.google.com/group/json-rpc/) server implementation.
It supports both the [JSON-RPC version 1 specification](http://json-rpc.org/wiki/specification) as
well as the [version 2 specification](http://www.jsonrpc.org/specification); additionally, it
provides a *PHP* implementation of the [Service Mapping Description (SMD)
specification](http://www.jsonrpc.org/specification) for providing service metadata to service
consumers.
JSON-RPC is a lightweight Remote Procedure Call protocol that utilizes *JSON* for its messaging
envelopes. This JSON-RPC implementation follows *PHP*'s
[SoapServer](http://www.php.net/manual/en/class.soapserver.php) *API*. This means, in a typical
situation, you will simply:
- Instantiate the server object
- Attach one or more functions and/or classes/objects to the server object
- handle() the request
`Zend\Json\Server` utilizes \[Zend\\Server\\Reflection\](zend.server.reflection) to perform
reflection on any attached classes or functions, and uses that information to build both the SMD and
enforce method call signatures. As such, it is imperative that any attached functions and/or class
methods have full *PHP* docblocks documenting, minimally:
- All parameters and their expected variable types
- The return value variable type
`Zend\Json\Server` listens for POST requests only at this time; fortunately, most JSON-RPC client
implementations in the wild at the time of this writing will only POST requests as it is. This makes
it simple to utilize the same server end point to both handle requests as well as to deliver the
service SMD, as is shown in the next example.
## Basic Usage
First, let's define a class we wish to expose via the JSON-RPC server. We'll call the class
'Calculator', and define methods for 'add', 'subtract', 'multiply', and 'divide':
```php
/**
* Calculator - sample class to expose via JSON-RPC
*/
class Calculator
{
/**
* Return sum of two variables
*
* @param int $x
* @param int $y
* @return int
*/
public function add($x, $y)
{
return $x + $y;
}
/**
* Return difference of two variables
*
* @param int $x
* @param int $y
* @return int
*/
public function subtract($x, $y)
{
return $x - $y;
}
/**
* Return product of two variables
*
* @param int $x
* @param int $y
* @return int
*/
public function multiply($x, $y)
{
return $x * $y;
}
/**
* Return the division of two variables
*
* @param int $x
* @param int $y
* @return float
*/
public function divide($x, $y)
{
return $x / $y;
}
}
```
Note that each method has a docblock with entries indicating each parameter and its type, as well as
an entry for the return value. This is **absolutely critical** when utilizing `Zend\Json\Server` or
any other server component in Zend Framework, for that matter.
Now we'll create a script to handle the requests:
```php
$server = new Zend\Json\Server\Server();
// Indicate what functionality is available:
$server->setClass('Calculator');
// Handle the request:
$server->handle();
```
However, this will not address the issue of returning an SMD so that the JSON-RPC client can
autodiscover methods. That can be accomplished by determining the *HTTP* request method, and then
specifying some server metadata:
```php
$server = new Zend\Json\Server\Server();
$server->setClass('Calculator');
if ('GET' == $_SERVER['REQUEST_METHOD']) {
// Indicate the URL endpoint, and the JSON-RPC version used:
$server->setTarget('/json-rpc.php')
->setEnvelope(Zend\Json\Server\Smd::ENV_JSONRPC_2);
// Grab the SMD
$smd = $server->getServiceMap();
// Return the SMD to the client
header('Content-Type: application/json');
echo $smd;
return;
}
$server->handle();
```
If utilizing the JSON-RPC server with Dojo toolkit, you will also need to set a special
compatibility flag to ensure that the two interoperate properly:
```php
$server = new Zend\Json\Server\Server();
$server->setClass('Calculator');
if ('GET' == $_SERVER['REQUEST_METHOD']) {
$server->setTarget('/json-rpc.php')
->setEnvelope(Zend\Json\Server\Smd::ENV_JSONRPC_2);
$smd = $server->getServiceMap();
// Set Dojo compatibility:
$smd->setDojoCompatible(true);
header('Content-Type: application/json');
echo $smd;
return;
}
$server->handle();
```
## Advanced Details
While most functionality for `Zend\Json\Server` is spelled out in \[this
section\](zend.json.server.usage), more advanced functionality is available.
### Zend\\Json\\Server\\Server
`Zend\Json\Server\Server` is the core class in the JSON-RPC offering; it handles all requests and
returns the response payload. It has the following methods:
- `addFunction($function)`: Specify a userland function to attach to the server.
- `setClass($class)`: Specify a class or object to attach to the server; all public methods of that
item will be exposed as JSON-RPC methods.
- `fault($fault = null, $code = 404, $data = null)`: Create and return a `Zend\Json\Server\Error`
object.
- `handle($request = false)`: Handle a JSON-RPC request; optionally, pass a
`Zend\Json\Server\Request` object to utilize (creates one by default).
- `getFunctions()`: Return a list of all attached methods.
- `setRequest(Zend\Json\Server\Request $request)`: Specify a request object for the server to
utilize.
- `getRequest()`: Retrieve the request object used by the server.
- `setResponse(Zend\Json\Server\Response $response)`: Set the response object for the server to
utilize.
- `getResponse()`: Retrieve the response object used by the server.
- `setAutoEmitResponse($flag)`: Indicate whether the server should automatically emit the response
and all headers; by default, this is `TRUE`.
- `autoEmitResponse()`: Determine if auto-emission of the response is enabled.
- `getServiceMap()`: Retrieve the service map description in the form of a `Zend\Json\Server\Smd`
object
### Zend\\Json\\Server\\Request
The JSON-RPC request environment is encapsulated in the `Zend\Json\Server\Request` object. This
object allows you to set necessary portions of the JSON-RPC request, including the request ID,
parameters, and JSON-RPC specification version. It has the ability to load itself via *JSON* or a
set of options, and can render itself as *JSON* via the `toJson()` method.
The request object has the following methods available:
- `setOptions(array $options)`: Specify object configuration. `$options` may contain keys matching
any 'set' method: `setParams()`, `setMethod()`, `setId()`, and `setVersion()`.
- `addParam($value, $key = null)`: Add a parameter to use with the method call. Parameters can be
just the values, or can optionally include the parameter name.
- `addParams(array $params)`: Add multiple parameters at once; proxies to `addParam()`
- `setParams(array $params)`: Set all parameters at once; overwrites any existing parameters.
- `getParam($index)`: Retrieve a parameter by position or name.
- `getParams()`: Retrieve all parameters at once.
- `setMethod($name)`: Set the method to call.
- `getMethod()`: Retrieve the method that will be called.
- `isMethodError()`: Determine whether or not the request is malformed and would result in an error.
- `setId($name)`: Set the request identifier (used by the client to match requests to responses).
- `getId()`: Retrieve the request identifier.
- `setVersion($version)`: Set the JSON-RPC specification version the request conforms to. May be
either '1.0' or '2.0'.
- `getVersion()`: Retrieve the JSON-RPC specification version used by the request.
- `loadJson($json)`: Load the request object from a *JSON* string.
- `toJson()`: Render the request as a *JSON* string.
An *HTTP* specific version is available via `Zend\Json\Server\Request\Http`. This class will
retrieve the request via `php://input`, and allows access to the raw *JSON* via the `getRawJson()`
method.
### Zend\\Json\\Server\\Response
The JSON-RPC response payload is encapsulated in the `Zend\Json\Server\Response` object. This object
allows you to set the return value of the request, whether or not the response is an error, the
request identifier, the JSON-RPC specification version the response conforms to, and optionally the
service map.
The response object has the following methods available:
- `setResult($value)`: Set the response result.
- `getResult()`: Retrieve the response result.
- `setError(Zend\Json\Server\Error $error)`: Set an error object. If set, this will be used as the
response when serializing to *JSON*.
- `getError()`: Retrieve the error object, if any.
- `isError()`: Whether or not the response is an error response.
- `setId($name)`: Set the request identifier (so the client may match the response with the original
request).
- `getId()`: Retrieve the request identifier.
- `setVersion($version)`: Set the JSON-RPC version the response conforms to.
- `getVersion()`: Retrieve the JSON-RPC version the response conforms to.
- `toJson()`: Serialize the response to *JSON*. If the response is an error response, serializes the
error object.
- `setServiceMap($serviceMap)`: Set the service map object for the response.
- `getServiceMap()`: Retrieve the service map object, if any.
An *HTTP* specific version is available via `Zend\Json\Server\Response\Http`. This class will send
the appropriate *HTTP* headers as well as serialize the response as *JSON*.
### Zend\\Json\\Server\\Error
JSON-RPC has a special format for reporting error conditions. All errors need to provide, minimally,
an error message and error code; optionally, they can provide additional data, such as a backtrace.
Error codes are derived from those recommended by [the XML-RPC EPI
project](http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php). `Zend\Json\Server`
appropriately assigns the code based on the error condition. For application exceptions, the code
'-32000' is used.
`Zend\Json\Server\Error` exposes the following methods:
- `setCode($code)`: Set the error code; if the code is not in the accepted XML-RPC error code range,
-32000 will be assigned.
- `getCode()`: Retrieve the current error code.
- `setMessage($message)`: Set the error message.
- `getMessage()`: Retrieve the current error message.
- `setData($data)`: Set auxiliary data further qualifying the error, such as a backtrace.
- `getData()`: Retrieve any current auxiliary error data.
- `toArray()`: Cast the error to an array. The array will contain the keys 'code', 'message', and
'data'.
- `toJson()`: Cast the error to a JSON-RPC error representation.
### Zend\\Json\\Server\\Smd
SMD stands for Service Mapping Description, a *JSON* schema that defines how a client can interact
with a particular web service. At the time of this writing, the
[specification](http://www.jsonrpc.org/specification) has not yet been formally ratified, but it is
in use already within Dojo toolkit as well as other JSON-RPC consumer clients.
At its most basic, a Service Mapping Description indicates the method of transport (POST, `GET`,
*TCP*/IP, etc), the request envelope type (usually based on the protocol of the server), the target
*URL* of the service provider, and a map of services available. In the case of JSON-RPC, the service
map is a list of available methods, which each method documenting the available parameters and their
types, as well as the expected return value type.
`Zend\Json\Server\Smd` provides an object-oriented way to build service maps. At its most basic, you
pass it metadata describing the service using mutators, and specify services (methods and
functions).
The service descriptions themselves are typically instances of `Zend\Json\Server\Smd\Service`; you
can also pass all information as an array to the various service mutators in `Zend\Json\Server\Smd`,
and it will instantiate a service for you. The service objects contain information such as the name
of the service (typically the function or method name), the parameters (names, types, and position),
and the return value type. Optionally, each service can have its own target and envelope, though
this functionality is rarely used.
`Zend\Json\Server\Server` actually does all of this behind the scenes for you, by using reflection
on the attached classes and functions; you should create your own service maps only if you need to
provide custom functionality that class and function introspection cannot offer.
Methods available in `Zend\Json\Server\Smd` include:
- `setOptions(array $options)`: Setup an SMD object from an array of options. All mutators (methods
beginning with 'set') can be used as keys.
- `setTransport($transport)`: Set the transport used to access the service; only POST is currently
supported.
- `getTransport()`: Get the current service transport.
- `setEnvelope($envelopeType)`: Set the request envelope that should be used to access the service.
Currently, supports the constants `Zend\Json\Server\Smd::ENV_JSONRPC_1` and
`Zend\Json\Server\Smd::ENV_JSONRPC_2`.
- `getEnvelope()`: Get the current request envelope.
- `setContentType($type)`: Set the content type requests should use (by default, this is
'application/json').
- `getContentType()`: Get the current content type for requests to the service.
- `setTarget($target)`: Set the *URL* endpoint for the service.
- `getTarget()`: Get the *URL* endpoint for the service.
- `setId($id)`: Typically, this is the *URL* endpoint of the service (same as the target).
- `getId()`: Retrieve the service ID (typically the *URL* endpoint of the service).
- `setDescription($description)`: Set a service description (typically narrative information
describing the purpose of the service).
- `getDescription()`: Get the service description.
- `setDojoCompatible($flag)`: Set a flag indicating whether or not the SMD is compatible with Dojo
toolkit. When `TRUE`, the generated *JSON* SMD will be formatted to comply with the format that
Dojo's JSON-RPC client expects.
- `isDojoCompatible()`: Returns the value of the Dojo compatibility flag (`FALSE`, by default).
- `addService($service)`: Add a service to the map. May be an array of information to pass to the
constructor of `Zend\Json\Server\Smd\Service`, or an instance of that class.
- `addServices(array $services)`: Add multiple services at once.
- `setServices(array $services)`: Add multiple services at once, overwriting any previously set
services.
- `getService($name)`: Get a service by its name.
- `getServices()`: Get all attached services.
- `removeService($name)`: Remove a service from the map.
- `toArray()`: Cast the service map to an array.
- `toDojoArray()`: Cast the service map to an array compatible with Dojo Toolkit.
- `toJson()`: Cast the service map to a *JSON* representation.
`Zend\Json\Server\Smd\Service` has the following methods:
- `setOptions(array $options)`: Set object state from an array. Any mutator (methods beginning with
'set') may be used as a key and set via this method.
- `setName($name)`: Set the service name (typically, the function or method name).
- `getName()`: Retrieve the service name.
- `setTransport($transport)`: Set the service transport (currently, only transports supported by
`Zend\Json\Server\Smd` are allowed).
- `getTransport()`: Retrieve the current transport.
- `setTarget($target)`: Set the *URL* endpoint of the service (typically, this will be the same as
the overall SMD to which the service is attached).
- `getTarget()`: Get the *URL* endpoint of the service.
- `setEnvelope($envelopeType)`: Set the service envelope (currently, only envelopes supported by
`Zend\Json\Server\Smd` are allowed).
- `getEnvelope()`: Retrieve the service envelope type.
- `addParam($type, array $options = array(), $order = null)`: Add a parameter to the service. By
default, only the parameter type is necessary. However, you may also specify the order, as well as
options such as:
- **name**: the parameter name
- **optional**: whether or not the parameter is optional
- **default**: a default value for the parameter
- **description**: text describing the parameter
- `addParams(array $params)`: Add several parameters at once; each param should be an assoc array
containing minimally the key 'type' describing the parameter type, and optionally the key 'order';
any other keys will be passed as `$options` to `addOption()`.
- `setParams(array $params)`: Set many parameters at once, overwriting any existing parameters.
- `getParams()`: Retrieve all currently set parameters.
- `setReturn($type)`: Set the return value type of the service.
- `getReturn()`: Get the return value type of the service.
- `toArray()`: Cast the service to an array.
- `toJson()`: Cast the service to a *JSON* representation.

View File

@@ -0,0 +1,98 @@
# XML to JSON conversion
`Zend\Json` provides a convenience method for transforming *XML* formatted data into *JSON* format.
This feature was inspired from an [IBM developerWorks
article](http://www.ibm.com/developerworks/xml/library/x-xml2jsonphp/).
`Zend\Json` includes a static function called `Zend\Json\Json::fromXml()`. This function will
generate *JSON* from a given *XML* input. This function takes any arbitrary *XML* string as an input
parameter. It also takes an optional boolean input parameter to instruct the conversion logic to
ignore or not ignore the *XML* attributes during the conversion process. If this optional input
parameter is not given, then the default behavior is to ignore the *XML* attributes. This function
call is made as shown below:
```php
// fromXml function simply takes a String containing XML contents
// as input.
$jsonContents = Zend\Json\Json::fromXml($xmlStringContents, true);
```
`Zend\Json\Json::fromXml()` function does the conversion of the *XML* formatted string input
parameter and returns the equivalent *JSON* formatted string output. In case of any *XML* input
format error or conversion logic error, this function will throw an exception. The conversion logic
also uses recursive techniques to traverse the *XML* tree. It supports recursion upto 25 levels
deep. Beyond that depth, it will throw a `Zend\Json\Exception`. There are several *XML* files with
varying degree of complexity provided in the tests directory of Zend Framework. They can be used to
test the functionality of the xml2json feature.
## Example
The following is a simple example that shows both the *XML* input string passed to and the *JSON*
output string returned as a result from the `Zend\Json\Json::fromXml()` function. This example used
the optional function parameter as not to ignore the *XML* attributes during the conversion. Hence,
you can notice that the resulting *JSON* string includes a representation of the *XML* attributes
present in the *XML* input string.
*XML* input string passed to `Zend\Json\Json::fromXml()` function:
```php
<?xml version="1.0" encoding="UTF-8"?>
<books>
<book id="1">
<title>Code Generation in Action</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>Manning</publisher>
</book>
<book id="2">
<title>PHP Hacks</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>O'Reilly</publisher>
</book>
<book id="3">
<title>Podcasting Hacks</title>
<author><first>Jack</first><last>Herrington</last></author>
<publisher>O'Reilly</publisher>
</book>
</books>
```
*JSON* output string returned from `Zend\Json\Json::fromXml()` function:
```php
{
"books" : {
"book" : [ {
"@attributes" : {
"id" : "1"
},
"title" : "Code Generation in Action",
"author" : {
"first" : "Jack", "last" : "Herrington"
},
"publisher" : "Manning"
}, {
"@attributes" : {
"id" : "2"
},
"title" : "PHP Hacks", "author" : {
"first" : "Jack", "last" : "Herrington"
},
"publisher" : "O'Reilly"
}, {
"@attributes" : {
"id" : "3"
},
"title" : "Podcasting Hacks", "author" : {
"first" : "Jack", "last" : "Herrington"
},
"publisher" : "O'Reilly"
}
]}
}
```
More details about this xml2json feature can be found in the original proposal itself. Take a look
at the [Zend\_xml2json
proposal](http://framework.zend.com/wiki/display/ZFPROP/Zend_xml2json+-+Senthil+Nathan).

View File

@@ -0,0 +1,11 @@
{
"title": "Zend\\Json",
"target": "html/",
"content": [
"book/zend.json.introduction.md",
"book/zend.json.basics.md",
"book/zend.json.objects.md",
"book/zend.json.xml2json.md",
"book/zend.json.server.md"
]
}

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;
}
}