Compare commits
292 commits
Author | SHA1 | Date | |
---|---|---|---|
|
531d0ee931 | ||
|
562d1e4cd0 | ||
fc78e5897b | |||
|
0a862dd86b | ||
5a3547894a | |||
1d05f3efeb | |||
831ed64871 | |||
502a0c8641 | |||
4a99094294 | |||
|
c379815f76 | ||
27e9e8eaa5 | |||
|
c21a89c761 | ||
068a9d0e0b | |||
|
1881dd3499 | ||
|
47edbda927 | ||
|
91c3ca8ce5 | ||
|
6d05a1af3f | ||
|
16cdc6ce49 | ||
cdca2b6d6d | |||
|
ecaac435cc | ||
|
e3cc485873 | ||
4880ed9930 | |||
7b393e961b | |||
3a3d00aeb8 | |||
60624dad9b | |||
121dcebb32 | |||
|
80396691dd | ||
|
3d5e3e92e6 | ||
|
6130b265fe | ||
|
da8ce13736 | ||
|
1c6ebd819f | ||
|
a7d071af28 | ||
544d16186f | |||
|
dc9e0a5b39 | ||
5b04bd9d8a | |||
|
ca1650d8d9 | ||
54ce958c76 | |||
|
be71f22c65 | ||
|
a7cffe45da | ||
8b677de616 | |||
|
0ef461e504 | ||
|
7d65407c7c | ||
|
ccbff3aba3 | ||
71a3a66724 | |||
|
36bf34b511 | ||
ca955cd49f | |||
|
a2bac7dcd0 | ||
bc5d044282 | |||
|
0298a1ad58 | ||
|
b7ae093cc4 | ||
f04a352126 | |||
|
e3877587bb | ||
fda9208de2 | |||
5bb006588b | |||
759c1ee5a8 | |||
18f5b8c196 | |||
312dfeffd7 | |||
7a4d755f83 | |||
f53f545c22 | |||
8ced4dc8a1 | |||
49844e50d6 | |||
0d747fa1e7 | |||
232c22a557 | |||
4de6a3b798 | |||
82f110d0d0 | |||
|
7cf7cbf467 | ||
|
3927344dde | ||
72636563ff | |||
|
37e9ca0110 | ||
af8a79379b | |||
|
a2d7035655 | ||
|
0fce4a49db | ||
|
f37434af17 | ||
|
2310b3b825 | ||
|
f926b59e36 | ||
fe18dce66a | |||
3054974756 | |||
|
a7bb610a0a | ||
725209296c | |||
|
a682ce606a | ||
|
5223d0e9f6 | ||
|
2d0ed4c149 | ||
454a5fac26 | |||
c10f2d3d47 | |||
|
37069e8cdd | ||
|
ef7d5cba25 | ||
b497e37c8e | |||
19f2b3dcd1 | |||
974b0cc8ef | |||
|
b6afd33906 | ||
|
2b02c0e116 | ||
|
6515e39144 | ||
|
a68705055c | ||
|
bb5a205cd1 | ||
|
8476f8946e | ||
fa0e8a7075 | |||
a87630a72b | |||
|
06e34486ac | ||
|
aa3c99fa6d | ||
|
86d280ba47 | ||
|
a42ddcc337 | ||
|
847fbd0bd0 | ||
|
47741acd28 | ||
|
bc820d0a69 | ||
|
12d8381b80 | ||
|
5e2c947f27 | ||
|
b0080ff23c | ||
|
186b44b0d4 | ||
f75a653a7d | |||
c47b6d2bb4 | |||
9a56566a7f | |||
ccd3c07872 | |||
d960d43053 | |||
|
bbb9af3ac0 | ||
040cb49d29 | |||
|
66690ac5dd | ||
|
1c68383869 | ||
fc62d95dbb | |||
df9d1b6e6f | |||
3aa3e65bbe | |||
937b0e4b76 | |||
dd419cb1ef | |||
5b69fc77cf | |||
36524fc85b | |||
|
48ed7e168a | ||
c1719bf84a | |||
a5ed9af862 | |||
|
13caebdbc2 | ||
|
0b6cbb6794 | ||
|
c6950a2103 | ||
|
e206a69d4a | ||
|
c796400ccb | ||
|
d71a74edcc | ||
123a59fdf8 | |||
|
2ada519c10 | ||
7124c816b5 | |||
|
f16cd5f6e7 | ||
|
972cd2ce68 | ||
5c35b3e3be | |||
|
8b136b075b | ||
|
6a8f9ec589 | ||
795ae316ff | |||
e76bb59970 | |||
|
8c05f269fa | ||
75fd10d40e | |||
|
f0a1adfb3d | ||
|
649b60b392 | ||
|
f472ca1fde | ||
46954b290c | |||
fdc01456d2 | |||
5a38c078c7 | |||
|
f2d9be5d4a | ||
0b6ed08d49 | |||
|
c8be9c7891 | ||
|
a17a816198 | ||
|
0ada1cd922 | ||
bce2bfaf4c | |||
|
9c6029fa0d | ||
|
fa93c2bc8d | ||
ed6f672cda | |||
|
cefb9fabcf | ||
822058c241 | |||
|
1a5c972842 | ||
b78088d794 | |||
|
fa85db6b7f | ||
|
2282f2bb4c | ||
f97fa8366f | |||
fe3dd0134a | |||
|
3d666332c3 | ||
a250b9d28e | |||
7d3ae80fe7 | |||
|
cfa975db17 | ||
153bc6344e | |||
d7c36b2cc7 | |||
|
1bdf8be34c | ||
d0ebd84cc8 | |||
|
7040640b57 | ||
|
b17385ffb6 | ||
8899cd6ee2 | |||
b2bd3ddfe1 | |||
|
0e2867c9d9 | ||
962ff0b1da | |||
|
6ed0d7eb0d | ||
|
1109578208 | ||
|
db549ab357 | ||
|
0a0bb0e461 | ||
|
d77cb53f10 | ||
|
f8ba3b1503 | ||
|
063fe1f921 | ||
|
b66801d7d2 | ||
|
d566d2b344 | ||
|
2672fcdebf | ||
|
e68a23885e | ||
|
5d0e20581c | ||
|
88cb6526b0 | ||
|
e3b452a0c2 | ||
d9128fa5d1 | |||
|
963eb19bfd | ||
|
4073580394 | ||
|
30dfcc7c68 | ||
8ebcdaeebc | |||
9bbf277d92 | |||
|
5371a9750e | ||
|
0f05f84d04 | ||
|
73b5b8e1df | ||
|
170e0b8f34 | ||
|
29fd360e4f | ||
8cd0837aa0 | |||
|
2ab21e1ea6 | ||
b4eccc73af | |||
|
96f345c657 | ||
df0177ac5e | |||
|
8069c0fcbe | ||
|
8084401dc6 | ||
|
6714723dc2 | ||
|
934d3ba751 | ||
|
275721aea7 | ||
|
ae1c1fa360 | ||
|
9ba0785e73 | ||
|
73d7f71c99 | ||
|
0951e3cfb3 | ||
|
2e4e24f507 | ||
|
1826dd57d6 | ||
|
5d1760c245 | ||
|
372029e1a2 | ||
|
4d59f37980 | ||
|
d1a73d7acd | ||
|
251c21300a | ||
|
6e48a1d222 | ||
|
5e890c9324 | ||
|
d61ab837e8 | ||
|
6947dfe08c | ||
|
403d04d3c4 | ||
|
440c7fcd8c | ||
|
c6c5f737bd | ||
|
ada3de8770 | ||
|
95c24bed4a | ||
|
93c204f322 | ||
|
aa79e774f1 | ||
|
25f9010793 | ||
|
3dd663e7a6 | ||
|
9aab02c189 | ||
|
4f57734e92 | ||
|
a18767bddc | ||
|
b701fa232c | ||
|
40fd0a2f70 | ||
|
ce2dab2c03 | ||
|
c9384ff54b | ||
|
d9bc74c9d4 | ||
|
cf814c5aae | ||
|
8c583de05b | ||
|
0fad2158be | ||
|
afd681201a | ||
|
2c77999fcc | ||
|
7a60213a7e | ||
|
11bae943f2 | ||
|
5c4413a97c | ||
|
5be40d1d09 | ||
|
0168ce754a | ||
|
64c24fab33 | ||
|
1f662a020a | ||
|
c79fe5b0ab | ||
|
e4870f1e29 | ||
|
82df6d5c4c | ||
|
51b53cfcc1 | ||
|
b1e7dbc52c | ||
|
c0c7ac6a7e | ||
|
c1c0f5af8c | ||
|
f2d51b017e | ||
|
3dc1fbb7a9 | ||
|
c5ddf84813 | ||
|
b6b49d5430 | ||
|
49f253c228 | ||
|
8233a670bc | ||
|
edf300dfad | ||
|
15b5a2b736 | ||
|
acf29c8c1b | ||
|
ddf46fd73d | ||
|
ffaa661384 | ||
|
de70ffd2bd | ||
|
a9b60cfd62 | ||
|
a6e5afa165 | ||
|
d26f609e9f | ||
|
546010b337 | ||
|
4ccf30d588 | ||
|
494a4d3432 | ||
|
338452a169 | ||
|
0c213eac0e | ||
|
a65fc7ff94 | ||
|
347d620959 | ||
|
4606062af8 | ||
|
838b850d48 |
890 changed files with 88500 additions and 3531 deletions
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{less,css,yml,json}]
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
6
.env.dist
Normal file
6
.env.dist
Normal file
|
@ -0,0 +1,6 @@
|
|||
# This file can be used change api url and api key in tests. Useful when you want to use real networking for test case.
|
||||
# Follow these steps if you want to use real networking for your test case:
|
||||
# - Replace credentials below with your own.
|
||||
# - Use `php-http/curl-client` as an argument for `TestClientFactory::createClient`.
|
||||
API_URL=https://test.retailcrm.pro/
|
||||
API_KEY=testkey
|
46
.github/workflows/ci.yml
vendored
Normal file
46
.github/workflows/ci.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags-ignore:
|
||||
- '*.*'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: "PHPUnit"
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
||||
steps:
|
||||
- name: Check out code into the workspace
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP ${{ matrix.php-version }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
coverage: pcov
|
||||
|
||||
- name: Composer cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.HOME }}/.composer/cache
|
||||
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install -o
|
||||
|
||||
- name: Configure matchers
|
||||
uses: mheap/phpunit-matcher-action@v1
|
||||
|
||||
- name: Run tests
|
||||
run: composer run-script phpunit-ci
|
||||
|
||||
- name: Coverage
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
verbose: true
|
42
.github/workflows/code_quality.yml
vendored
Normal file
42
.github/workflows/code_quality.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: "Code Quality Check"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.php"
|
||||
- "phpcs.xml"
|
||||
- ".github/workflows/code_quality.yml"
|
||||
|
||||
jobs:
|
||||
phpcs:
|
||||
name: "PHP CodeSniffer"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the workspace
|
||||
uses: actions/checkout@v4
|
||||
- name: Run PHPCS
|
||||
uses: chekalsky/phpcs-action@v1
|
||||
phpmd:
|
||||
name: "PHP MessDetector"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the workspace
|
||||
uses: actions/checkout@v4
|
||||
- name: Run PHPMD
|
||||
uses: GeneaLabs/action-reviewdog-phpmd@1.0.0
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
level: 'warning'
|
||||
reporter: github-pr-check
|
||||
standard: './phpmd.xml'
|
||||
target_directory: 'src'
|
||||
phpstan:
|
||||
name: PHPStan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the workspace
|
||||
uses: actions/checkout@v4
|
||||
- name: Run PHPStan
|
||||
uses: docker://oskarstark/phpstan-ga:1.8.0
|
||||
with:
|
||||
args: analyse src -c phpstan.neon --memory-limit=1G --no-progress
|
47
.github/workflows/documentation.yml
vendored
Normal file
47
.github/workflows/documentation.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
name: phpDocumentor
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
name: "phpDocumentor"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check GitHub Pages status
|
||||
if: ${{ github.ref != 'refs/heads/master' }}
|
||||
uses: crazy-max/ghaction-github-status@v2
|
||||
with:
|
||||
pages_threshold: major_outage
|
||||
- name: Check out code into the workspace
|
||||
if: success() && ${{ github.ref != 'refs/heads/master' }}
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup PHP 8.3
|
||||
if: ${{ github.ref != 'refs/heads/master' }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.3"
|
||||
- name: Cache phpDocumentor
|
||||
id: cache-phpdocumentor
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: phpDocumentor.phar
|
||||
key: phpdocumentor
|
||||
- name: Download latest phpDocumentor
|
||||
if: steps.cache-phpdocumentor.outputs.cache-hit != 'true'
|
||||
run: curl -O -L https://github.com/phpDocumentor/phpDocumentor/releases/download/v3.4.3/phpDocumentor.phar
|
||||
- name: Generate documentation
|
||||
if: ${{ github.ref != 'refs/heads/master' }}
|
||||
run: php phpDocumentor.phar
|
||||
- name: Deploy documentation to GitHub Pages
|
||||
if: ${{ github.ref != 'refs/heads/master' }}
|
||||
uses: crazy-max/ghaction-github-pages@v2
|
||||
with:
|
||||
target_branch: gh-pages
|
||||
build_dir: docs/build/html
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
24
.gitignore
vendored
24
.gitignore
vendored
|
@ -1,8 +1,25 @@
|
|||
# Composer files.
|
||||
/vendor
|
||||
/bin
|
||||
composer.lock
|
||||
composer.phar
|
||||
|
||||
# Code Quality tools artifacts.
|
||||
coverage.xml
|
||||
test-report.xml
|
||||
phpunit.xml
|
||||
.php_cs.cache
|
||||
.phpunit.result.cache
|
||||
|
||||
# phpDocumentor files.
|
||||
.phpdoc
|
||||
docs
|
||||
phpDocumentor.phar
|
||||
|
||||
# Ignore autogenerated code.
|
||||
models/*.php
|
||||
models/checksum.json
|
||||
|
||||
# Different environment-related files.
|
||||
.idea
|
||||
.DS_Store
|
||||
.settings
|
||||
|
@ -10,3 +27,8 @@ phpunit.xml
|
|||
.project
|
||||
.swp
|
||||
/nbproject
|
||||
.env
|
||||
|
||||
.docker
|
||||
docker*
|
||||
Makefile
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 RetailDriver LLC
|
||||
Copyright (c) 2015-2020 RetailDriver LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
322
README.md
322
README.md
|
@ -1,102 +1,286 @@
|
|||
# retailCRM API PHP client
|
||||
[](https://github.com/retailcrm/api-client-php/actions)
|
||||
[](https://codecov.io/gh/retailcrm/api-client-php)
|
||||
[](https://packagist.org/packages/retailcrm/api-client-php)
|
||||
[](https://packagist.org/packages/retailcrm/api-client-php)
|
||||
|
||||
PHP-client for [retailCRM API](http://www.retailcrm.pro/docs/Developers/ApiVersion3).
|
||||
|
||||
Use [API documentation](http://retailcrm.github.io/api-client-php)
|
||||
# RetailCRM API PHP client
|
||||
|
||||
This is the PHP RetailCRM API client. This library allows using of the actual API version.
|
||||
You can find more info in the [documentation](doc/index.md).
|
||||
|
||||
# Table of contents
|
||||
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Examples](#examples)
|
||||
* [Notes](#notes)
|
||||
* [Documentation](doc/index.md)
|
||||
|
||||
## Requirements
|
||||
|
||||
* PHP 5.3 and above
|
||||
* PHP 7.3 and above
|
||||
* PHP's cURL support
|
||||
* PHP's JSON support
|
||||
* Any HTTP client compatible with PSR-18 (covered by the installation instructions).
|
||||
* Any HTTP factories implementation compatible with PSR-17 (covered by the installation instructions).
|
||||
* Any HTTP messages implementation compatible with PSR-7 (covered by the installation instructions).
|
||||
* Other dependencies listed in the `composer.json` (covered by the installation instructions)
|
||||
|
||||
## Install
|
||||
## Installation
|
||||
|
||||
1) Get [composer](https://getcomposer.org/download/)
|
||||
Follow those steps to install the library:
|
||||
|
||||
2) Run into your project directory:
|
||||
1. Download and install [Composer](https://getcomposer.org/download/) package manager.
|
||||
2. Install the library from the Packagist by executing this command:
|
||||
```bash
|
||||
composer require retailcrm/api-client-php ~3.0.0 --no-dev
|
||||
composer require retailcrm/api-client-php:"~6.0"
|
||||
```
|
||||
During the installation you will see this message. Press `'y'` when you do:
|
||||
```sh
|
||||
civicrm/composer-compile-plugin contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
|
||||
Do you trust "civicrm/composer-compile-plugin" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
|
||||
```
|
||||
After that, you may see a message which will look like this:
|
||||
```sh
|
||||
The following packages have new compilation tasks:
|
||||
- retailcrm/api-client-php has 1 task
|
||||
|
||||
Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp)
|
||||
```
|
||||
|
||||
If you have not used `composer` before, include autoloader into your project.
|
||||
Choose `[a]lways` by typing `a` and pressing Enter.
|
||||
|
||||
**Note:** You should choose `'y'` and `[a]lways` if your application is using CI/CD pipeline because the interactive terminal is not available
|
||||
in that environment which will result in failure during the dependencies installation.
|
||||
|
||||
If you chose something else during the installation and API client doesn't work properly - please follow [these instructions](doc/compilation_prompt.md#ive-chosen-something-else-now-api-client-doesnt-work) to fix the problem.
|
||||
|
||||
3. Include the autoloader if it's not included, or you didn't use Composer before.
|
||||
```php
|
||||
require 'path/to/vendor/autoload.php';
|
||||
```
|
||||
|
||||
Replace `path/to/vendor/autoload.php` with the correct path to Composer's `autoload.php`.
|
||||
|
||||
**Note:** API client uses `php-http/curl-client` and `nyholm/psr7` as a PSR-18, PSR-17 and PSR-7 implementation.
|
||||
You can replace those implementations during installation by installing this library with the implementation of your choice, like this:
|
||||
```sh
|
||||
composer require symfony/http-client guzzlehttp/psr7 retailcrm/api-client-php:"~6.0"
|
||||
```
|
||||
|
||||
More information about that can be found in the [documentation](doc/customization/different_psr_implementations.md).
|
||||
|
||||
## Usage
|
||||
|
||||
### Get order
|
||||
```php
|
||||
$client = new \RetailCrm\ApiClient(
|
||||
'https://demo.retailcrm.ru',
|
||||
'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH'
|
||||
);
|
||||
Firstly, you should initialize the Client. The easiest way to do this is to use the `SimpleClientFactory`:
|
||||
|
||||
```php
|
||||
$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
```
|
||||
|
||||
The client is separated into several resource groups, all of which are accessible through the Client's public properties.
|
||||
You can call API methods from those groups like this:
|
||||
|
||||
```php
|
||||
$client->api->credentials();
|
||||
```
|
||||
|
||||
For example, you can retrieve the customers list:
|
||||
|
||||
```php
|
||||
$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
$response = $client->customers->list();
|
||||
```
|
||||
|
||||
Or the orders list:
|
||||
|
||||
```php
|
||||
$client = \RetailCrm\Api\Factory\SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
$response = $client->orders->list();
|
||||
```
|
||||
|
||||
To handle errors you must use two types of exceptions:
|
||||
* `RetailCrm\Api\Interfaces\ClientExceptionInterface` for the network or other runtime errors.
|
||||
* `RetailCrm\Api\Interfaces\ApiExceptionInterface` for the errors from the API.
|
||||
|
||||
An example of error handling can be found in the next section of this document.
|
||||
|
||||
Each resource group is responsible for the corresponding API section. For example, `costs` resource group provide methods
|
||||
for costs manipulation and `loyalty` resource group allows interacting with loyalty programs, accounts, bonuses, etc.
|
||||
|
||||
Use annotations to determine which DTOs you need for sending the requests. If annotations are not provided by your IDE - you
|
||||
probably should configure them. It'll ease your work with this (and any other) library a lot.
|
||||
|
||||
More information about the usage including examples can be found in the [documentation](doc/usage/usage.md).
|
||||
|
||||
## Examples
|
||||
|
||||
Listing orders:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
try {
|
||||
$response = $client->ordersGet('M-2342');
|
||||
} catch (\RetailCrm\Exception\CurlException $e) {
|
||||
echo "Connection error: " . $e->getMessage();
|
||||
$response = $client->orders->list();
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface and ClientExceptionInterface instance implements __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
echo $response->order['totalSumm'];
|
||||
// or $response['order']['totalSumm'];
|
||||
// or
|
||||
// $order = $response->getOrder();
|
||||
// $order['totalSumm'];
|
||||
} else {
|
||||
echo sprintf(
|
||||
"Error: [HTTP-code %s] %s",
|
||||
$response->getStatusCode(),
|
||||
$response->getErrorMsg()
|
||||
);
|
||||
foreach ($response->orders as $order) {
|
||||
printf("Order ID: %d\n", $order->id);
|
||||
printf("First name: %s\n", $order->firstName);
|
||||
printf("Last name: %s\n", $order->lastName);
|
||||
printf("Patronymic: %s\n", $order->patronymic);
|
||||
printf("Phone #1: %s\n", $order->phone);
|
||||
printf("Phone #2: %s\n", $order->additionalPhone);
|
||||
printf("E-Mail: %s\n", $order->email);
|
||||
|
||||
// error details
|
||||
//if (isset($response['errors'])) {
|
||||
// print_r($response['errors']);
|
||||
//}
|
||||
if ($order->customer instanceof CustomerCorporate) {
|
||||
echo "Customer type: corporate\n";
|
||||
} else {
|
||||
echo "Customer type: individual\n";
|
||||
}
|
||||
|
||||
foreach ($order->items as $item) {
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Product name: %s\n", $item->productName);
|
||||
printf("Quantity: %d\n", $item->quantity);
|
||||
printf("Initial price: %f\n", $item->initialPrice);
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Discount: %f\n", $order->discountManualAmount);
|
||||
printf("Total: %f\n", $order->totalSumm);
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### Create order
|
||||
```php
|
||||
Fetching a specific order by it's ID:
|
||||
|
||||
$client = new \RetailCrm\ApiClient(
|
||||
'https://demo.retailcrm.ru',
|
||||
'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH'
|
||||
);
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Enum\ByIdentifier;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Request\BySiteRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
try {
|
||||
$response = $client->ordersCreate(array(
|
||||
'externalId' => 'some-shop-order-id',
|
||||
'firstName' => 'Vasily',
|
||||
'lastName' => 'Pupkin',
|
||||
'items' => array(
|
||||
//...
|
||||
),
|
||||
'delivery' => array(
|
||||
'code' => 'russian-post',
|
||||
)
|
||||
));
|
||||
} catch (\RetailCrm\Exception\CurlException $e) {
|
||||
echo "Connection error: " . $e->getMessage();
|
||||
$response = $client->orders->get(1234, new BySiteRequest(ByIdentifier::ID, 'site'));
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if ($response->isSuccessful() && 201 === $response->getStatusCode()) {
|
||||
echo 'Order successfully created. Order ID into retailCRM = ' . $response->id;
|
||||
// or $response['id'];
|
||||
// or $response->getId();
|
||||
} else {
|
||||
echo sprintf(
|
||||
"Error: [HTTP-code %s] %s",
|
||||
$response->getStatusCode(),
|
||||
$response->getErrorMsg()
|
||||
);
|
||||
|
||||
// error details
|
||||
//if (isset($response['errors'])) {
|
||||
// print_r($response['errors']);
|
||||
//}
|
||||
}
|
||||
echo 'Order: ' . print_r($response->order, true);
|
||||
```
|
||||
|
||||
Creating a new customer:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
||||
use RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
$request = new CustomersCreateRequest();
|
||||
$request->customer = new Customer();
|
||||
|
||||
$request->site = 'aliexpress';
|
||||
$request->customer->email = 'john.doe@example.com';
|
||||
$request->customer->firstName = 'John';
|
||||
$request->customer->lastName = 'Doe';
|
||||
|
||||
try {
|
||||
$response = $client->customers->create($request);
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
echo 'Customer ID: ' . $response->id;
|
||||
```
|
||||
|
||||
Creating a task for the user with a specific email:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Entity\Tasks\Task;use RetailCrm\Api\Model\Filter\Users\ApiUserFilter;
|
||||
use RetailCrm\Api\Model\Request\Tasks\TasksCreateRequest;
|
||||
use RetailCrm\Api\Model\Request\Users\UsersRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
$usersRequest = new UsersRequest();
|
||||
$usersRequest->filter = new ApiUserFilter();
|
||||
$usersRequest->filter->email = 'john.doe@example.com';
|
||||
|
||||
try {
|
||||
$usersResponse = $client->users->list($usersRequest);
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (0 === count($usersResponse->users)) {
|
||||
echo 'User is not found.';
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
$tasksRequest = new TasksCreateRequest();
|
||||
$tasksRequest->task = new Task();
|
||||
$tasksRequest->task->performerId = $usersResponse->users[0]->id;
|
||||
$tasksRequest->task->text = 'Do something!';
|
||||
$tasksRequest->site = 'site';
|
||||
|
||||
try {
|
||||
$tasksResponse = $client->tasks->create($tasksRequest);
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
echo 'Created task with ID: ' . $tasksResponse->id;
|
||||
```
|
||||
|
||||
|
||||
The error handling in the examples above is good enough for real production usage.
|
||||
You can safely assume that `ApiExceptionInterface` is an error from the API, and `ClientExceptionInterface` is a client error
|
||||
(e.g. network error or any runtime error, use `HttpClientException` to catch only PSR-18 client errors).
|
||||
However, you can implement more complex error handling if you want.
|
||||
|
||||
Also, both `ApiExceptionInterface` and `ClientExceptionInterface` implements `__toString()`. This means that you can just
|
||||
convert those exceptions to string and put the results into logs without any special treatment for the exception data.
|
||||
|
||||
More examples can be found in the [documentation](doc/usage/examples/index.md).
|
||||
|
||||
You can use a PSR-14 compatible event dispatcher to receive events from the client. See [documentation](doc/index.md) for details.
|
||||
|
||||
## Notes
|
||||
|
||||
This library uses HTTPlug abstractions. Visit [official documentation](http://docs.php-http.org/en/latest/httplug/users.html) to learn more about it.
|
||||
|
|
104
README.ru.md
104
README.ru.md
|
@ -1,104 +0,0 @@
|
|||
# PHP-клиент для retailCRM API
|
||||
|
||||
PHP-клиент для работы с [retailCRM API](http://www.retailcrm.ru/docs/Developers/ApiVersion3).
|
||||
|
||||
Рекомендуем обращаться к [документации](http://retailcrm.github.io/api-client-php) по библиотеке, в частности по классу [RetailCrm\ApiClient](http://retailcrm.github.io/api-client-php/class-RetailCrm.ApiClient.html).
|
||||
|
||||
## Обязательные требования
|
||||
|
||||
* PHP версии 5.3 и выше
|
||||
* PHP-расширение cURL
|
||||
|
||||
## Установка
|
||||
|
||||
1) Установите [composer](https://getcomposer.org/download/)
|
||||
|
||||
2) Выполните в папке проекта:
|
||||
```bash
|
||||
composer require retailcrm/api-client-php ~3.0.0 --no-dev
|
||||
```
|
||||
|
||||
В конфиг `composer.json` вашего проекта будет добавлена библиотека `retailcrm/api-client-php`, которая установится в папку `vendor/`. При отсутствии файла конфига или папки с вендорами они будут созданы.
|
||||
|
||||
В случае, если до этого в вашем проекте не использовался `composer`, подключите файл автозагрузки вендоров. Для этого укажите в коде проекта:
|
||||
```php
|
||||
require 'path/to/vendor/autoload.php';
|
||||
```
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Получение информации о заказе
|
||||
```php
|
||||
$client = new \RetailCrm\ApiClient(
|
||||
'https://demo.retailcrm.ru',
|
||||
'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH'
|
||||
);
|
||||
|
||||
|
||||
try {
|
||||
$response = $client->ordersGet('M-2342');
|
||||
} catch (\RetailCrm\Exception\CurlException $e) {
|
||||
echo "Сетевые проблемы. Ошибка подключения к retailCRM: " . $e->getMessage();
|
||||
}
|
||||
|
||||
if ($response->isSuccessful()) {
|
||||
echo $response->order['totalSumm'];
|
||||
// или $response['order']['totalSumm'];
|
||||
// или
|
||||
// $order = $response->getOrder();
|
||||
// $order['totalSumm'];
|
||||
} else {
|
||||
echo sprintf(
|
||||
"Ошибка получения информации о заказа: [Статус HTTP-ответа %s] %s",
|
||||
$response->getStatusCode(),
|
||||
$response->getErrorMsg()
|
||||
);
|
||||
|
||||
// получить детализацию ошибок
|
||||
//if (isset($response['errors'])) {
|
||||
// print_r($response['errors']);
|
||||
//}
|
||||
}
|
||||
```
|
||||
|
||||
### Создание заказа
|
||||
```php
|
||||
|
||||
$client = new \RetailCrm\ApiClient(
|
||||
'https://demo.retailcrm.ru',
|
||||
'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH'
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $client->ordersCreate(array(
|
||||
'externalId' => 'some-shop-order-id',
|
||||
'firstName' => 'Vasily',
|
||||
'lastName' => 'Pupkin',
|
||||
'items' => array(
|
||||
//...
|
||||
),
|
||||
'delivery' => array(
|
||||
'code' => 'russian-post',
|
||||
)
|
||||
));
|
||||
} catch (\RetailCrm\Exception\CurlException $e) {
|
||||
echo "Сетевые проблемы. Ошибка подключения к retailCRM: " . $e->getMessage();
|
||||
}
|
||||
|
||||
if ($response->isSuccessful() && 201 === $response->getStatusCode()) {
|
||||
echo 'Заказ успешно создан. ID заказа в retailCRM = ' . $response->id;
|
||||
// или $response['id'];
|
||||
// или $response->getId();
|
||||
} else {
|
||||
echo sprintf(
|
||||
"Ошибка создания заказа: [Статус HTTP-ответа %s] %s",
|
||||
$response->getStatusCode(),
|
||||
$response->getErrorMsg()
|
||||
);
|
||||
|
||||
// получить детализацию ошибок
|
||||
//if (isset($response['errors'])) {
|
||||
// print_r($response['errors']);
|
||||
//}
|
||||
}
|
||||
```
|
36
apigen.neon
36
apigen.neon
|
@ -1,36 +0,0 @@
|
|||
extensions:
|
||||
- php
|
||||
|
||||
source:
|
||||
- lib
|
||||
|
||||
exclude:
|
||||
- tests/
|
||||
- vendor/
|
||||
- bin/
|
||||
- docs/
|
||||
|
||||
charset:
|
||||
- auto
|
||||
- UTF-8
|
||||
- Windows-1251
|
||||
|
||||
title: retailCRM PHP API client
|
||||
templateTheme: bootstrap
|
||||
groups: auto
|
||||
autocomplete:
|
||||
- classes
|
||||
- constants
|
||||
- methods
|
||||
- classconstants
|
||||
|
||||
accessLevels:
|
||||
- public
|
||||
|
||||
internal: true
|
||||
php: false
|
||||
tree: true
|
||||
deprecated: true
|
||||
todo: true
|
||||
destination: ../api-client-php.pages/
|
||||
download: false
|
49
bin/retailcrm-client
Executable file
49
bin/retailcrm-client
Executable file
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
namespace RetailCrm\Api;
|
||||
|
||||
use ReflectionClass;
|
||||
use RetailCrm\Api\Component\ComposerLocator;
|
||||
use RetailCrm\Api\Component\PhpFilesIterator;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
if (!in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) {
|
||||
echo 'Warning: The console should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
|
||||
}
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
require __DIR__ . '/../src/Component/ComposerLocator.php';
|
||||
|
||||
$composerAutoloader = ComposerLocator::findAutoloader();
|
||||
|
||||
if ('' === $composerAutoloader) {
|
||||
echo 'Cannot find autoload.php. Please install dependencies first.' . PHP_EOL;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
require $composerAutoloader;
|
||||
|
||||
if (!class_exists('Symfony\Component\Console\Application')) {
|
||||
echo 'Cannot find Symfony\Component\Console\Application class. Please install dependencies first.';
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
$application = new Application();
|
||||
$commands = new PhpFilesIterator(implode(DIRECTORY_SEPARATOR, [dirname(__DIR__), 'src', 'Command']));
|
||||
|
||||
foreach ($commands as $command) {
|
||||
if (!array_key_exists('fqn', $command)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$commandFqn = $command['fqn'];
|
||||
|
||||
if (class_exists($commandFqn) && !(new ReflectionClass($commandFqn))->isAbstract()) {
|
||||
$application->add(new $commandFqn());
|
||||
}
|
||||
}
|
||||
|
||||
$application->setName('RetailCRM API Client Management Tool');
|
||||
$application->run();
|
124
composer.json
124
composer.json
|
@ -1,43 +1,121 @@
|
|||
{
|
||||
"name": "retailcrm/api-client-php",
|
||||
"description": "PHP client for retailCRM API",
|
||||
"description": "PHP client for RetailCRM API",
|
||||
"type": "library",
|
||||
"keywords": ["API", "retailCRM", "REST"],
|
||||
"homepage": "http://www.retailcrm.ru/",
|
||||
"keywords": [
|
||||
"API",
|
||||
"RetailCRM",
|
||||
"REST"
|
||||
],
|
||||
"homepage": "http://www.retailcrm.pro/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "retailCRM",
|
||||
"email": "support@retailcrm.ru"
|
||||
"name": "RetailCRM",
|
||||
"email": "support@retailcrm.pro"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"ext-curl": "*"
|
||||
"php": ">=7.3",
|
||||
"ext-json": "*",
|
||||
"psr/log": "^1|^2|^3",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-message-implementation": "^1.0",
|
||||
"php-http/client-implementation": "^1.0",
|
||||
"php-http/discovery": "^1.13",
|
||||
"doctrine/annotations": "^1.13|^2.0",
|
||||
"liip/serializer": "2.2.* || 2.6.*",
|
||||
"php-http/httplug": "^2.2",
|
||||
"civicrm/composer-compile-plugin": "^0.20",
|
||||
"symfony/console": "^4.0|^5.0|^6.0|^7.0",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"neur0toxine/psr.http-client-implementation.php-http-curl": "*",
|
||||
"neur0toxine/psr.http-factory-implementation.nyholm": "*",
|
||||
"neur0toxine/psr.http-message-implementation.nyholm": "*",
|
||||
"psr/cache": "^1.0 || ^2.0 || ^3.0",
|
||||
"symfony/cache": ">=v3.1.0",
|
||||
"psr/http-factory": "^1.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.2.*",
|
||||
"phpunit/php-code-coverage": "3.3.0",
|
||||
"phpunit/php-invoker": "1.1.4",
|
||||
"phpmd/phpmd": "2.4.*",
|
||||
"sebastian/phpcpd": "2.0.*",
|
||||
"sebastian/phpdcd": "1.0.*",
|
||||
"squizlabs/php_codesniffer": "2.5.*",
|
||||
"apigen/apigen": "4.1.*"
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"phpmd/phpmd": "^2.10",
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "1.9.14",
|
||||
"vlucas/phpdotenv": "^5.3",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"php-http/curl-client": "^2.2",
|
||||
"nyholm/psr7": "^1.3",
|
||||
"league/event": "^3.0",
|
||||
"league/container": "^3.3",
|
||||
"neur0toxine/pock": "^0.11"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Most HTTP clients need ext-curl to work properly.",
|
||||
"php-http/client-implementation": "PSR-18 compatible client should be available to use this library.",
|
||||
"psr/http-message-implementation": "PSR-7 compatible HTTP message implementation should be available to use to use this library.",
|
||||
"psr/http-factory-implementation": "PSR-17 compatible factories should be available to use this library.",
|
||||
"symfony/event-dispatcher": "PSR-14 compatible event dispatcher.",
|
||||
"league/event": "PSR-14 compatible event dispatcher.",
|
||||
"nyholm/psr7": "This is recommended PSR-7 and PSR-17 implementation.",
|
||||
"php-http/curl-client": "Simplest PSR-18 client implementation.",
|
||||
"symfony/http-client": "One of the most popular HTTP clients. Has PSR-18 compatible adapter.",
|
||||
"psr/log-implementation": "You can use log implementation for debug purposes."
|
||||
},
|
||||
"scripts": {
|
||||
"phpunit": "./vendor/bin/phpunit -c phpunit.xml.dist --coverage-text",
|
||||
"phpunit-ci": "@php -dpcov.enabled=1 -dpcov.directory=. -dpcov.exclude=\"~vendor~\" ./vendor/bin/phpunit --teamcity -c phpunit.xml.dist",
|
||||
"phpmd": "./vendor/bin/phpmd src text ./phpmd.xml",
|
||||
"phpcs": "./vendor/bin/phpcs -p src --runtime-set testVersion 7.3-8.3 && ./vendor/bin/phpcs -p tests --runtime-set testVersion 7.3-8.3 --warning-severity=0",
|
||||
"phpstan": "./vendor/bin/phpstan analyse -c phpstan.neon src --memory-limit=-1",
|
||||
"phpstan-dockerized-ci": "docker run --rm -it -w=/app -v ${PWD}:/app oskarstark/phpstan-ga:1.0.1 analyse src -c phpstan.neon --memory-limit=1G --no-progress",
|
||||
"lint:fix": "./vendor/bin/phpcbf src",
|
||||
"lint": [
|
||||
"@phpcs",
|
||||
"@phpmd",
|
||||
"@phpstan"
|
||||
],
|
||||
"verify": [
|
||||
"@lint",
|
||||
"@phpunit"
|
||||
],
|
||||
"models": "@php bin/retailcrm-client models:generate --all"
|
||||
},
|
||||
"support": {
|
||||
"email": "support@intarocrm.ru"
|
||||
"email": "support@retailcrm.pro"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": { "RetailCrm\\": "lib/" }
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0.x-dev"
|
||||
"psr-4": {
|
||||
"RetailCrm\\Api\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"RetailCrm\\TestUtils\\": "tests/utils/",
|
||||
"RetailCrm\\Tests\\": "tests/src/"
|
||||
}
|
||||
},
|
||||
"bin": [
|
||||
"bin/retailcrm-client"
|
||||
],
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.x-dev"
|
||||
},
|
||||
"compile": [
|
||||
{
|
||||
"run": "@composer run-script models"
|
||||
}
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"bin-dir": "bin",
|
||||
"process-timeout": 600
|
||||
"bin-dir": "vendor/bin",
|
||||
"process-timeout": 600,
|
||||
"allow-plugins": {
|
||||
"civicrm/composer-compile-plugin": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
"php-http/discovery": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
109
doc/compilation_prompt.md
Normal file
109
doc/compilation_prompt.md
Normal file
|
@ -0,0 +1,109 @@
|
|||
## Dealing with `civicrm/composer-compile-plugin` prompts
|
||||
|
||||
During installation you will see this prompt:
|
||||
```sh
|
||||
civicrm/composer-compile-plugin contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
|
||||
Do you trust "civicrm/composer-compile-plugin" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
|
||||
```
|
||||
|
||||
And after almost any Composer operation you will see this prompt:
|
||||
```sh
|
||||
The following packages have new compilation tasks:
|
||||
- retailcrm/api-client-php has 1 task
|
||||
|
||||
Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp)
|
||||
```
|
||||
|
||||
That's because the API client utilizes code generation to speed up the serialization and deserialization of the requests. However,
|
||||
these prompts may be annoying and sometimes can even break the application lifecycle pipeline (in the CI/CD environment). We can't just
|
||||
disable it for everyone [because of security concerns](https://github.com/composer/composer/issues/1193). But you can disable it for your project.
|
||||
|
||||
There are three ways of disabling this prompt:
|
||||
1. During the installation.
|
||||
2. Automated way.
|
||||
3. Manual way.
|
||||
|
||||
### Disable compilation prompts during the installation
|
||||
|
||||
Press `'y'` when you see this message:
|
||||
```sh
|
||||
civicrm/composer-compile-plugin contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins
|
||||
Do you trust "civicrm/composer-compile-plugin" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?]
|
||||
```
|
||||
|
||||
And when you see this prompt, press `'a'`:
|
||||
```sh
|
||||
The following packages have new compilation tasks:
|
||||
- retailcrm/api-client-php has 1 task
|
||||
|
||||
Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp)
|
||||
```
|
||||
|
||||
That's it. Code generation is now enabled.
|
||||
|
||||
### I've chosen something else, now API client doesn't work!
|
||||
|
||||
That happens. We provide special CLI utility which will automatically configure your `composer.json` to enable code generation.
|
||||
Just run this command inside your project after API client installation:
|
||||
```sh
|
||||
./vendor/bin/retailcrm-client compiler:prompt
|
||||
```
|
||||
|
||||
You should see this message after running the command:
|
||||
```sh
|
||||
✓ Done, code generation has been enabled.
|
||||
```
|
||||
|
||||
You may also want to run code generation manually once. It can be achieved by running this command:
|
||||
```sh
|
||||
composer compile --all
|
||||
```
|
||||
|
||||
**Note:** `retailcrm-client` should be in your binary directory. By default it is set to `vendor/bin`. You can check `config.bin-dir`
|
||||
value in your `composer.json` and update paths in the commands above accordingly.
|
||||
**Note (2):** `compiler:prompt` command has `--revert` flag. You can use it if you want to disable automatic code generation for some reason.
|
||||
|
||||
### Disabling compilation prompts manually
|
||||
|
||||
It is possible to replicate the same actions manually. First, you will need to enable compiler plugin. Add the plugin
|
||||
to the `config.allow-plugins` segment of your `composer.json` file:
|
||||
|
||||
```json
|
||||
"allow-plugins": {
|
||||
"civicrm/composer-compile-plugin": true
|
||||
}
|
||||
```
|
||||
|
||||
After that add these params into the `extra` segment of your `composer.json`:
|
||||
|
||||
```json
|
||||
"compile-mode": "whitelist",
|
||||
"compile-whitelist": ["retailcrm/api-client-php"]
|
||||
```
|
||||
|
||||
Your `composer.json` file should look like this:
|
||||
```json
|
||||
{
|
||||
"name": "author/some-project",
|
||||
"description": "Description of the project.",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": ">=7.3.0",
|
||||
"symfony/http-client": "^5.2",
|
||||
"nyholm/psr7": "^1.4",
|
||||
"retailcrm/api-client-php": "~6.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"civicrm/composer-compile-plugin": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"compile-mode": "whitelist",
|
||||
"compile-whitelist": ["retailcrm/api-client-php"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Voilà! You won't see the annoying prompt again.
|
20
doc/customization/customization.md
Normal file
20
doc/customization/customization.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
## Customization
|
||||
|
||||
* [Controlling HTTP abstraction layer](different_psr_implementations.md)
|
||||
* [Customizing request and response processing](pipelines/implementing_a_handler.md)
|
||||
+ [Using a predefined handler](pipelines/using_a_predefined_handler.md)
|
||||
+ [Built-in handlers](pipelines/using_a_predefined_handler.md#built-in-handlers)
|
||||
+ [Modifying the default pipeline](pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline)
|
||||
+ [Constructing the pipeline from scratch](pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch)
|
||||
+ [Implementing a handler](pipelines/implementing_a_handler.md)
|
||||
* [Implementing custom API methods](implementing_custom_api_methods.md)
|
||||
|
||||
Both `ClientFactory` and `ClientBuilder` provide the necessary functionality to replace PSR dependencies with any other compatible implementation.
|
||||
By default, those dependencies will be detected via service discovery. But service discovery supports a limited amount of implementation.
|
||||
If your implementation is not supported - the client won't work unless you provide the necessary dependencies manually.
|
||||
Another case would be testing. You can provide special HTTP client implementation which will return mocked responses instead of making
|
||||
real requests.
|
||||
|
||||
The Client uses [chain of responsibility](https://refactoring.guru/design-patterns/chain-of-responsibility) pattern to process requests.
|
||||
Each request and response is being processed by the pipeline of handlers. Every handler can apply some mutation to the request or response
|
||||
and can pass it to the next handler. In fact, API authentication is made possible by using a special handler which appends API key to every request.
|
69
doc/customization/different_psr_implementations.md
Normal file
69
doc/customization/different_psr_implementations.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
## Controlling HTTP abstraction layer
|
||||
|
||||
You can replace default PSR dependencies in the client while using `ClientFactory` or `ClientBuilder`. It can be useful for tests
|
||||
or if you want to use a specific implementation that is not supported by the service discovery.
|
||||
|
||||
Both `ClientFactory` and `ClientBuilder` provide those methods:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Set your PSR-18 HTTP client.
|
||||
*
|
||||
* Service discovery will be used if no client has been provided.
|
||||
*
|
||||
* @param \Psr\Http\Client\ClientInterface $httpClient
|
||||
*/
|
||||
public function setHttpClient(\Psr\Http\Client\ClientInterface $httpClient);
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible stream factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory
|
||||
*/
|
||||
public function setStreamFactory(?\Psr\Http\Message\StreamFactoryInterface $streamFactory);
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible request factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory
|
||||
*/
|
||||
public function setRequestFactory(?\Psr\Http\Message\RequestFactoryInterface $requestFactory);
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible URI factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\UriFactoryInterface|null $uriFactory
|
||||
*/
|
||||
public function setUriFactory(?\Psr\Http\Message\UriFactoryInterface $uriFactory);
|
||||
```
|
||||
|
||||
They can be used to specify PSR dependencies like this:
|
||||
|
||||
```php
|
||||
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||
$factory = new \RetailCrm\Api\Factory\ClientFactory();
|
||||
$factory->setHttpClient(new \Http\Client\Curl\Client())
|
||||
->setRequestFactory($psr17Factory)
|
||||
->setStreamFactory($psr17Factory)
|
||||
->setUriFactory($psr17Factory);
|
||||
|
||||
$client = $factory->createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
```
|
||||
|
||||
Or like this:
|
||||
|
||||
```php
|
||||
$psr17Factory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||
$builder = new \RetailCrm\Api\Builder\ClientBuilder();
|
||||
$client = $builder
|
||||
->setApiUrl('https://test.retailcrm.pro')
|
||||
->setAuthenticatorHandler(new \RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler('apiKey'))
|
||||
->setHttpClient(new \Http\Client\Curl\Client())
|
||||
->setRequestFactory($psr17Factory)
|
||||
->setStreamFactory($psr17Factory)
|
||||
->setUriFactory($psr17Factory)
|
||||
->build();
|
||||
```
|
||||
|
||||
By replacing the HTTP client in the test environment you can easily mock requests and responses via libraries like
|
||||
[`neur0toxine/pock`](https://packagist.org/packages/neur0toxine/pock) or [`php-http/mock-client`](https://packagist.org/packages/php-http/mock-client).
|
|
@ -0,0 +1,28 @@
|
|||
# Custom API methods with DTO
|
||||
|
||||
This example demonstrates how you can use your custom serializer with custom DTOs to implement API methods.
|
||||
|
||||
## How to run the project
|
||||
|
||||
1. Open `app.php` and change credentials and the site to your data.
|
||||
2. Run these commands:
|
||||
```sh
|
||||
composer install
|
||||
php app.php
|
||||
```
|
||||
|
||||
You will see something like this:
|
||||
```sh
|
||||
Created customer using custom methods. ID: 5633
|
||||
```
|
||||
|
||||
This means that the project works as expected.
|
||||
|
||||
## Navigation
|
||||
|
||||
- [`app.php`](app.php) - entrypoint, calls the custom method and outputs the response data.
|
||||
- [`src/Component/Adapter/SymfonyToLiipAdapter.php`](src/Component/Adapter/SymfonyToLiipAdapter.php) - adapter for using `symfony/serializer` inside `FormEncoder` component.
|
||||
- [`src/Component/CustomApiMethod.php`](src/Component/CustomApiMethod.php) - `CustomApiMethod` that uses `SerializerInterface` from `liip/serializer` and `FormEncoder`. This component will handle marshaling.
|
||||
- [`src/Dto`](src/Dto) - data models used in the project.
|
||||
- [`src/Factory/SerializerFactory.php`](src/Factory/SerializerFactory.php) - builds `symfony/serializer`'s serializer and wraps it into the `SymfonyToLiipAdapter`.
|
||||
- [`src/Factory/ClientFactory.php`](src/Factory/ClientFactory.php) - custom client factory that register the custom API method.
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
use RetailCrm\Api\Builder\FormEncoderBuilder;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Customer;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Request\CustomersCreateRequest;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Factory\ClientFactory;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Factory\SerializerFactory;
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Three lines below will be usually called during DI container building or hidden by other means.
|
||||
$serializer = SerializerFactory::create();
|
||||
$encoder = (new FormEncoderBuilder())->setSerializer($serializer)->build();
|
||||
$clientFactory = (new ClientFactory())->setCustomEncoder($encoder);
|
||||
|
||||
// Replace API URL and API key with your data.
|
||||
$client = $clientFactory->createClient('https://test.simla.com', 'apiKey');
|
||||
|
||||
$request = new CustomersCreateRequest();
|
||||
$request->customer = new Customer();
|
||||
$request->customer->firstName = 'Tester';
|
||||
$request->customer->lastName = 'User';
|
||||
$request->customer->patronymic = 'Patronymic';
|
||||
$request->site = 'site'; // Replace site with your data.
|
||||
|
||||
/** @var \Retailcrm\Examples\CustomMethodsDto\Dto\Response\CustomersCreateResponse $response */
|
||||
$response = $client->customMethods->createCustomer($request);
|
||||
|
||||
echo 'Created customer using custom methods. ID: ' . $response->id;
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "retailcrm/custom-api-methods-with-dto-example",
|
||||
"description": "This project demonstrates DTO usage with the custom methods.",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"retailcrm/api-client-php": "^6",
|
||||
"symfony/serializer": "^5.3",
|
||||
"symfony/property-access": "^5.3"
|
||||
},
|
||||
"license": "MIT",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"RetailCrm\\Examples\\CustomMethodsDto\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SymfonyToLiipAdapter
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component\Adapter
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Component\Adapter;
|
||||
|
||||
use Liip\Serializer\Context;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Symfony\Component\Serializer\Serializer as SymfonySerializer;
|
||||
|
||||
/**
|
||||
* Class SymfonyToLiipAdapter
|
||||
*
|
||||
* @category SymfonyToLiipAdapter
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component\Adapter
|
||||
*/
|
||||
class SymfonyToLiipAdapter implements SerializerInterface
|
||||
{
|
||||
/** @var SymfonySerializer */
|
||||
private $serializer;
|
||||
|
||||
public function __construct(SymfonySerializer $serializer)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function serialize($data, string $format, Context $context = null): string
|
||||
{
|
||||
return $this->serializer->serialize($data, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function deserialize(string $data, string $type, string $format, Context $context = null)
|
||||
{
|
||||
return $this->serializer->deserialize($data, $type, $format);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
*/
|
||||
public function toArray($data, Context $context = null): array
|
||||
{
|
||||
return $this->serializer->normalize($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
|
||||
*/
|
||||
public function fromArray(array $data, string $type, Context $context = null)
|
||||
{
|
||||
return $this->serializer->denormalize($data, $type);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Component;
|
||||
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
/**
|
||||
* Class CustomApiMethod
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Component
|
||||
*/
|
||||
class CustomApiMethod extends \RetailCrm\Api\Component\CustomApiMethod
|
||||
{
|
||||
/** @var string */
|
||||
private $responseFqn;
|
||||
|
||||
/** @var FormEncoderInterface */
|
||||
private $encoder;
|
||||
|
||||
public function __construct(string $method, string $route, string $responseFqn, FormEncoderInterface $encoder)
|
||||
{
|
||||
parent::__construct($method, $route);
|
||||
|
||||
$this->responseFqn = $responseFqn;
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request, returns the response.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestSenderInterface $sender
|
||||
* @param array<int|string, mixed>|object $data
|
||||
*
|
||||
* @return object
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function __invoke(RequestSenderInterface $sender, $data = [])
|
||||
{
|
||||
if (is_object($data)) {
|
||||
$data = $this->encoder->encodeArray($data);
|
||||
}
|
||||
|
||||
$result = parent::__invoke($sender, $data);
|
||||
|
||||
try {
|
||||
return $this->encoder->getSerializer()->fromArray($result, $this->responseFqn);
|
||||
} catch (\Throwable $throwable) {
|
||||
throw new HandlerException(
|
||||
'Cannot deserialize body: ' . $throwable->getMessage(),
|
||||
0,
|
||||
$throwable
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Customer
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto;
|
||||
|
||||
/**
|
||||
* Class Customer
|
||||
*
|
||||
* @category Customer
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto
|
||||
*/
|
||||
class Customer
|
||||
{
|
||||
/** @var string */
|
||||
public $firstName;
|
||||
|
||||
/** @var string */
|
||||
public $lastName;
|
||||
|
||||
/** @var string */
|
||||
public $patronymic;
|
||||
|
||||
/** @var string */
|
||||
public $email;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomersCreateRequest
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Request
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto\Request;
|
||||
|
||||
use RetailCrm\Api\Component\FormData\Mapping as Form;
|
||||
|
||||
/**
|
||||
* Class CustomersCreateRequest
|
||||
*
|
||||
* @category CustomersCreateRequest
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Request
|
||||
*/
|
||||
class CustomersCreateRequest
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*
|
||||
* @Form\Type("string")
|
||||
* @Form\SerializedName("site")
|
||||
*/
|
||||
public $site;
|
||||
|
||||
/**
|
||||
* @var \Retailcrm\Examples\CustomMethodsDto\Dto\Customer
|
||||
*
|
||||
* @Form\Type("Retailcrm\Examples\CustomMethodsDto\Dto\Customer")
|
||||
* @Form\SerializedName("customer")
|
||||
* @Form\JsonField()
|
||||
*/
|
||||
public $customer;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomersCreateResponse
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Response
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Dto\Response;
|
||||
|
||||
/**
|
||||
* Class CustomersCreateResponse
|
||||
*
|
||||
* @category CustomersCreateResponse
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Dto\Response
|
||||
*/
|
||||
class CustomersCreateResponse
|
||||
{
|
||||
/** @var int */
|
||||
public $id;
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ClientFactory
|
||||
* @package RetailCrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Examples\CustomMethodsDto\Factory;
|
||||
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Factory\ClientFactory as Base;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Component\CustomApiMethod;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Dto\Response\CustomersCreateResponse;
|
||||
|
||||
/**
|
||||
* Class ClientFactory
|
||||
*
|
||||
* @category ClientFactory
|
||||
* @package RetailCrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
class ClientFactory extends Base
|
||||
{
|
||||
/** @var \RetailCrm\Api\Interfaces\FormEncoderInterface */
|
||||
private $customEncoder;
|
||||
|
||||
public function setCustomEncoder(FormEncoderInterface $customEncoder): ClientFactory
|
||||
{
|
||||
$this->customEncoder = $customEncoder;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function createClient(string $apiUrl, string $apiKey): Client
|
||||
{
|
||||
$client = parent::createClient($apiUrl, $apiKey);
|
||||
$client->customMethods->register(
|
||||
'createCustomer',
|
||||
$this->method(
|
||||
RequestMethod::POST,
|
||||
'customers/create',
|
||||
CustomersCreateResponse::class
|
||||
)
|
||||
);
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
private function method(string $method, string $route, string $responseFqn): CustomApiMethod
|
||||
{
|
||||
return new CustomApiMethod($method, $route, $responseFqn, $this->customEncoder);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SerializerFactory
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
|
||||
namespace Retailcrm\Examples\CustomMethodsDto\Factory;
|
||||
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use RetailCrm\Examples\CustomMethodsDto\Component\Adapter\SymfonyToLiipAdapter;
|
||||
use Symfony\Component\Serializer\Serializer;
|
||||
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||||
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||||
|
||||
/**
|
||||
* Class SerializerFactory
|
||||
*
|
||||
* @category SerializerFactory
|
||||
* @package Retailcrm\Examples\CustomMethodsDto\Factory
|
||||
*/
|
||||
class SerializerFactory
|
||||
{
|
||||
public static function create(): SerializerInterface
|
||||
{
|
||||
return new SymfonyToLiipAdapter(new Serializer([new ObjectNormalizer()], [new JsonEncoder()]));
|
||||
}
|
||||
}
|
106
doc/customization/implementing_custom_api_methods.md
Normal file
106
doc/customization/implementing_custom_api_methods.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
## Implementing custom API methods
|
||||
|
||||
You can use this feature if you need to group multiple API calls, or return something custom - not the API response as-is,
|
||||
or, for example, to use new API methods without waiting for the implementation. For all of those cases, we provide a special
|
||||
resource group in the client that will make it easy to implement a custom API method.
|
||||
|
||||
The main limitation of this mechanism is the lack of DTO. You will be limited to arrays inside custom methods. It is
|
||||
_possible_ to use custom DTO's for the custom methods, but it is a little tricky - check the bottom of this page for the example.
|
||||
|
||||
Let's imagine that we have a special method with this route: `/api/v5/dialogs`. This method returns a list of the dialogs present in the system.
|
||||
We cannot use it directly because it is not implemented in the client. However, we can implement it by ourselves using custom methods.
|
||||
It will look like this:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Component\CustomApiMethod;
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.simla.io', 'key');
|
||||
$client->customMethods->register('dialogs', new CustomApiMethod(RequestMethod::GET, 'dialogs'));
|
||||
|
||||
try {
|
||||
$dialogs = $client->customMethods->call('dialogs');
|
||||
} catch (ApiExceptionInterface $exception) {
|
||||
echo sprintf(
|
||||
'Error from RetailCRM API (status code: %d): %s',
|
||||
$exception->getStatusCode(),
|
||||
$exception->getMessage()
|
||||
);
|
||||
|
||||
if (count($exception->getErrorResponse()->errors) > 0) {
|
||||
echo PHP_EOL . 'Errors: ' . implode(', ', $exception->getErrorResponse()->errors);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
echo 'Dialogs: ' . print_r($dialogs['dialogs'], true);
|
||||
```
|
||||
|
||||
First, we need to register the custom method in the client. To do this we must give our method a name and an implementation.
|
||||
The name will be used to call the method later using `call()` and the implementation is just a callable with the three
|
||||
parameters: `RequestSenderInterface`, data array, and the context. Let's take a look at all three.
|
||||
|
||||
1. `RequestSenderInterface` is implemented by the `RequestSender` and contains three methods: `send`, `route` and `host`.
|
||||
`send` is used to send the request, `route` will append the base URL to your method name, and the latter, `host`, will return
|
||||
hostname from the base URL.
|
||||
2. The data array contains any data that has been provided to the callable during the method call. It will be encoded as
|
||||
a query string and stored either as the URL params or as the form body (query string is used for GET and DELETE requests).
|
||||
3. The context contains any information provided to the callable during the method call. It is not checked by the client.
|
||||
However, it is always must be of array type.
|
||||
|
||||
As you can see, we're using `CustomApiMethod` as a callable in the example. The `CustomApiMethod` is a simple invokable
|
||||
wrapper that can be used to simplify the registration of the new methods. The boilerplate code in the previous example is
|
||||
already implemented inside the `CustomApiMethod` component.
|
||||
|
||||
It is easier to understand how the callable should work and how the `CustomApiMethod` works by looking at the registration example.
|
||||
It works exactly like the previous example, but without the `CustomApiMethod` component:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Enum\RequestMethod;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
$client->customMethods->register('dialogs', function (RequestSenderInterface $sender, $data, array $context) {
|
||||
return $sender->send(RequestMethod::GET, $sender->route('dialogs'), $data);
|
||||
});
|
||||
```
|
||||
|
||||
The data here is not defined as array because it can be anything you like. You can use any serializer to serialize the data
|
||||
and deserialize the response. `CustomApiMethod` only supports array out-of-box, but your handlers can utilize any
|
||||
data types.
|
||||
|
||||
The base URL inside the client is always represented as a URL with the version suffix. It should be always kept in mind
|
||||
while using the `route` method:
|
||||
|
||||
```php
|
||||
// This code is being executed inside the custom method callable.
|
||||
// Base URL is http://test.simla.io/api/v5
|
||||
|
||||
$settingsRoute = $sender->route('settings');
|
||||
$host = $sender->host();
|
||||
|
||||
echo $settingsRoute; // prints "https://test.simla.io/api/v5/settings" - the slash is inserted by the route() method.
|
||||
echo $host; // prints "test.simla.io"
|
||||
```
|
||||
|
||||
Any registered custom method can be called using the `__call` magic method:
|
||||
|
||||
```php
|
||||
// Both calls below works the same.
|
||||
|
||||
$client->customMethods->call('dialogs', ['param' => 'value'], ['contextParam' => 'contextValue']);
|
||||
$client->customMethods->dialogs(['param' => 'value'], ['contextParam' => 'contextValue']);
|
||||
```
|
||||
|
||||
The last notable difference from the regular methods lies inside events. You won't be able to get request or response models
|
||||
from the events, but you can extract the response array using the `getResponseArray()` method.
|
||||
|
||||
## Using DTOs with the custom methods
|
||||
|
||||
You can use DTOs with the custom methods. To do that you just need to serialize the request models inside the method callback
|
||||
before sending the request and deserialize the response before returning it. We already made a great example project that
|
||||
demonstrates the DTO usage alongside custom methods.
|
||||
|
||||
[**DTOs with the custom methods - sample project**](examples/custom-api-methods-with-dto)
|
23
doc/customization/pipelines/implementing_a_handler.md
Normal file
23
doc/customization/pipelines/implementing_a_handler.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
## Implementing a handler
|
||||
|
||||
You can implement your own handler using the `RetailCrm\Api\Interfaces\HandlerInterface`.
|
||||
`RetailCrm\Api\Handler\AbstractHandler` provides boilerplate code for the chain of responsibility.
|
||||
`AbstractHandler::next` method will call next handler in the chain. You can safely use `return parent::next()` in your code
|
||||
while using `AbstractHandler`.
|
||||
|
||||
Most of the information about how handlers operate can be found in the [chain of responsibility](https://refactoring.guru/design-patterns/chain-of-responsibility)
|
||||
pattern explanation. There are some specific details about handlers in the client. Client will pass desired dependencies to
|
||||
the handler if handler implements one of those interfaces:
|
||||
|
||||
* Any request handler
|
||||
+ `RetailCrm\Api\Interfaces\PsrFactoriesAwareInterface` will inject PSR-17 factories into the handler.
|
||||
* Any response handler
|
||||
+ `RetailCrm\Api\Interfaces\SerializerAwareInterface` will inject a `liip/serializer` instance into the handler.
|
||||
+ `RetailCrm\Api\Interfaces\ApiExceptionFactoryAwareInterface` will inject a `Liip\Serializer\SerializerInterface` instance into the handler.
|
||||
+ `RetailCrm\Api\Interfaces\EventDispatcherAwareInterface` will inject a `Psr\EventDispatcher\EventDispatcherInterface` instance into the handler.
|
||||
|
||||
All of those interfaces above have the corresponding implementation traits in the `RetailCrm\Api\Traits` namespace.
|
||||
For example, `RetailCrm\Api\Interfaces\EventDispatcherAwareInterface` implementation can be found in the
|
||||
`RetailCrm\Api\Traits\EventDispatcherAwareTrait` trait.
|
||||
|
||||
Handlers can be used in the both chains just like callback handlers (see ["using a predefined hander"](using_a_predefined_handler.md)).
|
292
doc/customization/pipelines/using_a_predefined_handler.md
Normal file
292
doc/customization/pipelines/using_a_predefined_handler.md
Normal file
|
@ -0,0 +1,292 @@
|
|||
## Using a predefined handler
|
||||
|
||||
* [Built-in handlers](#built-in-handlers)
|
||||
* [Modifying the default pipeline](#modifying-the-default-pipeline)
|
||||
* [Constructing the pipeline from scratch](#constructing-the-pipeline-from-scratch)
|
||||
|
||||
### Built-in handlers
|
||||
|
||||
You can use a predefined handler to modify the process of request or response processing. Most of the predefined handlers are
|
||||
already in use by the client, except for the `CallbackRequestHandler` and `CallbackResponseHandler`.
|
||||
|
||||
Those are handlers that are present in the client and can be used for request processing:
|
||||
* `CallbackRequestHandler` - processes a request using provided callback.
|
||||
* `GetParameterAuthenticatorHandler` - appends a GET `apiKey` parameter with the API key.
|
||||
* `HeaderAuthenticatorHandler` - appends `X-Api-Key` header with the API key.
|
||||
* `PsrRequestHandler` - constructs base PSR-7 request with method and URI.
|
||||
* `RequestDataHandler` - fills base PSR-7 request with the data from the DTO.
|
||||
|
||||
Request handlers are using `RetailCrm\Api\Model\RequestData` DTO to share state between handlers.
|
||||
|
||||
Those are handlers that can be used for response processing:
|
||||
* `AccountNotFoundHandler` - detects when RetailCRM with specified API URL is not exist and throws specified exception.
|
||||
* `CallbackResponseHandler` - processes a response using provided callback.
|
||||
* `ErrorResponseHandler` - detects an error response, throws correct exception.
|
||||
* `FilesDownloadResponseHandler` - handles `/api/v5/files/download` response.
|
||||
* `UnmarshalResponseHandler` - unmarshals response body into response DTO.
|
||||
|
||||
Response handlers are using `RetailCrm\Api\Model\ResponseData` DTO to share state between handlers.
|
||||
|
||||
There are two built-in handlers which are most useful to users who want to modify the processing pipelines: `CallbackRequestHandler` and
|
||||
`CallbackResponseHandler`. Both handlers use provided callbacks to modify a request or response. You can initialize those
|
||||
with the examples below:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Handler\Request\CallbackRequestHandler;
|
||||
use RetailCrm\Api\Model\RequestData;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Http\Message\UriFactoryInterface;
|
||||
|
||||
$handler = new CallbackRequestHandler(
|
||||
static function (
|
||||
RequestData $requestData,
|
||||
RequestFactoryInterface $requestFactory,
|
||||
StreamFactoryInterface $streamFactory,
|
||||
UriFactoryInterface $uriFactory
|
||||
) {
|
||||
if (null !== $requestData->request) {
|
||||
$requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token');
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Exception\Api\MissingParameterException;
|
||||
use RetailCrm\Api\Factory\ApiExceptionFactory;
|
||||
use RetailCrm\Api\Handler\Response\CallbackResponseHandler;
|
||||
use RetailCrm\Api\Model\Response\Api\Credentials;
|
||||
use RetailCrm\Api\Model\Response\ErrorResponse;
|
||||
use RetailCrm\Api\Model\ResponseData;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
$handler = new CallbackResponseHandler(
|
||||
static function (
|
||||
ResponseData $data,
|
||||
SerializerInterface $serializer,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ApiExceptionFactory $apiExceptionFactory
|
||||
) {
|
||||
if (
|
||||
$data->responseModel instanceof Credentials &&
|
||||
!in_array('/api/customers/create', $data->responseModel->credentials)
|
||||
) {
|
||||
$data->responseModel = new ErrorResponse();
|
||||
$data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing';
|
||||
|
||||
throw new MissingParameterException($data->responseModel, 400);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
Both handlers above can be appended to request and response pipelines. Let's move on to the next part to learn how
|
||||
you can append handlers to the chain using `ClientFactory` or `ClientBuilder`.
|
||||
|
||||
### Modifying the default pipeline
|
||||
|
||||
Two limitations apply to default pipeline modifying:
|
||||
1. Chain ordering cannot be changed. Your handler will always be at the end of the default chain.
|
||||
1. Callback handlers will always call the next handler. There is no way to stop the next handlers in the chain.
|
||||
|
||||
However, you can construct your own pipeline which won't be bound by the first limitation. And the second limitation can be
|
||||
circumvented by [implementing your own handler](implementing_a_handler.md).
|
||||
|
||||
Both `ClientFactory` and `ClientBuilder` allow you to append your own handlers to the processing pipeline. They provide
|
||||
those methods:
|
||||
|
||||
```php
|
||||
/**
|
||||
* Appends an additional request handler into the request processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface $handler
|
||||
*/
|
||||
public function appendRequestHandler(HandlerInterface $handler);
|
||||
|
||||
/**
|
||||
* Appends an additional handler into the response processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface $handler
|
||||
*/
|
||||
public function appendResponseHandler(HandlerInterface $handler);
|
||||
|
||||
/**
|
||||
* Appends an additional request handlers into the request processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers
|
||||
*/
|
||||
public function appendRequestHandlers(array $handlers);
|
||||
|
||||
/**
|
||||
* Appends an additional response handlers into the response processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers
|
||||
*/
|
||||
public function appendResponseHandlers(array $handlers);
|
||||
```
|
||||
|
||||
You can use those methods to pass the handler into desired chain. Take a look at the examples. This code will initialize the client
|
||||
with provided `CallbackRequestHandler`. Instantiation will be done via `ClientFactory`:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\ClientFactory;
|
||||
use RetailCrm\Api\Handler\Request\CallbackRequestHandler;
|
||||
use RetailCrm\Api\Model\RequestData;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Http\Message\UriFactoryInterface;
|
||||
|
||||
$handler = new CallbackRequestHandler(
|
||||
static function (
|
||||
RequestData $requestData,
|
||||
RequestFactoryInterface $requestFactory,
|
||||
StreamFactoryInterface $streamFactory,
|
||||
UriFactoryInterface $uriFactory
|
||||
) {
|
||||
if (null !== $requestData->request) {
|
||||
$requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$factory = new ClientFactory();
|
||||
$client = $factory->appendRequestHandler($handler)->createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
```
|
||||
|
||||
And this code will instantiate a client using `ClientBuilder`. But this time we're modifying response pipeline:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Builder\ClientBuilder;
|
||||
use RetailCrm\Api\Builder\FormEncoderBuilder;
|
||||
use RetailCrm\Api\Exception\Api\MissingParameterException;
|
||||
use RetailCrm\Api\Factory\ApiExceptionFactory;
|
||||
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
|
||||
use RetailCrm\Api\Handler\Response\CallbackResponseHandler;
|
||||
use RetailCrm\Api\Model\Response\Api\Credentials;
|
||||
use RetailCrm\Api\Model\Response\ErrorResponse;
|
||||
use RetailCrm\Api\Model\ResponseData;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
$handler = new CallbackResponseHandler(
|
||||
static function (
|
||||
ResponseData $data,
|
||||
SerializerInterface $serializer,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ApiExceptionFactory $apiExceptionFactory
|
||||
) {
|
||||
if (
|
||||
$data->responseModel instanceof Credentials &&
|
||||
!in_array('/api/customers/create', $data->responseModel->credentials)
|
||||
) {
|
||||
$data->responseModel = new ErrorResponse();
|
||||
$data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing';
|
||||
|
||||
throw new MissingParameterException($data->responseModel, 400);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build();
|
||||
$builder = new ClientBuilder();
|
||||
$client = $builder->setApiUrl('https://test.retailcrm.pro')
|
||||
->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey'))
|
||||
->setFormEncoder($formEncoder)
|
||||
->appendRequestHandler($handler)
|
||||
->build();
|
||||
```
|
||||
|
||||
That's how you can modify request and response processing chains. However, you also can use the default pipeline factory
|
||||
to pass desired handlers into the chain. Default request and response pipelines are constructed by those static factories:
|
||||
```php
|
||||
RetailCrm\Api\Factory\RequestPipelineFactory::createDefaultPipeline()
|
||||
RetailCrm\Api\Factory\ResponsePipelineFactory::createDefaultPipeline()
|
||||
```
|
||||
|
||||
Look at the example below to learn how to use those static factories. In this example we will modify both the request
|
||||
and response pipelines:
|
||||
|
||||
```php
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Http\Message\UriFactoryInterface;
|
||||
use RetailCrm\Api\Builder\ClientBuilder;
|
||||
use RetailCrm\Api\Builder\FormEncoderBuilder;
|
||||
use RetailCrm\Api\Component\Transformer\RequestTransformer;
|
||||
use RetailCrm\Api\Component\Transformer\ResponseTransformer;
|
||||
use RetailCrm\Api\Exception\Api\MissingParameterException;
|
||||
use RetailCrm\Api\Factory\ApiExceptionFactory;
|
||||
use RetailCrm\Api\Factory\RequestPipelineFactory;
|
||||
use RetailCrm\Api\Factory\ResponsePipelineFactory;
|
||||
use RetailCrm\Api\Handler\Request\CallbackRequestHandler;
|
||||
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
|
||||
use RetailCrm\Api\Handler\Response\CallbackResponseHandler;
|
||||
use RetailCrm\Api\Model\RequestData;
|
||||
use RetailCrm\Api\Model\Response\Api\Credentials;
|
||||
use RetailCrm\Api\Model\Response\ErrorResponse;
|
||||
use RetailCrm\Api\Model\ResponseData;
|
||||
|
||||
$requestHandler = new CallbackRequestHandler(
|
||||
static function (
|
||||
RequestData $requestData,
|
||||
RequestFactoryInterface $requestFactory,
|
||||
StreamFactoryInterface $streamFactory,
|
||||
UriFactoryInterface $uriFactory
|
||||
) {
|
||||
if (null !== $requestData->request) {
|
||||
$requestData->request = $requestData->request->withHeader('X-Rlimit-Token', 'example_token');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$responseHandler = new CallbackResponseHandler(
|
||||
static function (
|
||||
ResponseData $data,
|
||||
SerializerInterface $serializer,
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
ApiExceptionFactory $apiExceptionFactory
|
||||
) {
|
||||
if (
|
||||
$data->responseModel instanceof Credentials &&
|
||||
!in_array('/api/customers/create', $data->responseModel->credentials)
|
||||
) {
|
||||
$data->responseModel = new ErrorResponse();
|
||||
$data->responseModel->errorMsg = 'Parameter "/api/customers/create" is missing';
|
||||
|
||||
throw new MissingParameterException($data->responseModel, 400);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$builder = new ClientBuilder();
|
||||
$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build();
|
||||
$client = $builder->setApiUrl('https://test.retailcrm.pro')
|
||||
->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey'))
|
||||
->setRequestTransformer(new RequestTransformer(
|
||||
RequestPipelineFactory::createDefaultPipeline(
|
||||
$formEncoder,
|
||||
null, // PSR factories will be found by the service discovery.
|
||||
null,
|
||||
null,
|
||||
$requestHandler
|
||||
)
|
||||
))
|
||||
->setResponseTransformer(new ResponseTransformer(
|
||||
ResponsePipelineFactory::createDefaultPipeline(
|
||||
$formEncoder->getSerializer(),
|
||||
new ApiExceptionFactory(),
|
||||
null, // No EventDispatcherInterface was provided
|
||||
$responseHandler
|
||||
)
|
||||
))->build();
|
||||
```
|
||||
|
||||
### Constructing the pipeline from scratch
|
||||
|
||||
Both `RequestTransformer` and `ResponseTransformer` accept the `HandlerInterface` instance as the first argument. You can
|
||||
look into `RequestPipelineFactory` and `ResponsePipelineFactory` source code to learn how to build your own pipeline from
|
||||
scratch. This is, however, is discouraged.
|
32
doc/index.md
Normal file
32
doc/index.md
Normal file
|
@ -0,0 +1,32 @@
|
|||
# Documentation
|
||||
|
||||
* [Dealing with `civicrm/composer-compile-plugin` prompts](compilation_prompt.md)
|
||||
+ [Disable compilation prompts during the installation](compilation_prompt.md#disable-compilation-prompts-during-the-installation)
|
||||
+ [I've chosen something else, now API client doesn't work!](compilation_prompt.md#ive-chosen-something-else-now-api-client-doesnt-work)
|
||||
+ [Disabling compilation prompts manually](compilation_prompt.md#disabling-compilation-prompts-manually)
|
||||
* [Client structure](structure.md)
|
||||
+ [Design principles](structure.md#design-principles)
|
||||
+ [Resource groups](structure.md#resource-groups)
|
||||
* [Usage](usage/usage.md)
|
||||
+ [Instantiation](usage/instantiation.md)
|
||||
+ [Sending a request](usage/sending_a_request.md)
|
||||
+ [Choosing correct resource group, DTOs, and method](usage/sending_a_request.md#choosing-correct-resource-group-dtos-and-method)
|
||||
+ [Sending a request](usage/sending_a_request.md#sending-a-request)
|
||||
+ [Dealing with exceptions](usage/sending_a_request.md#dealing-with-exceptions)
|
||||
+ [Error handling](usage/error_handling.md)
|
||||
+ [Examples](usage/examples)
|
||||
+ [How to create an order](usage/examples/create_order.md)
|
||||
+ [How to receive the list of orders](usage/examples/fetch_orders.md)
|
||||
+ [How to handle all Client's exceptions](usage/examples/complete_error_handling_example.md)
|
||||
+ [Event handling](usage/event_handing.md)
|
||||
* [Customization](customization/customization.md)
|
||||
+ [Controlling HTTP abstraction layer](customization/different_psr_implementations.md)
|
||||
+ [Customizing request and response processing](customization/pipelines/implementing_a_handler.md)
|
||||
+ [Using a predefined handler](customization/pipelines/using_a_predefined_handler.md)
|
||||
+ [Built-in handlers](customization/pipelines/using_a_predefined_handler.md#built-in-handlers)
|
||||
+ [Modifying the default pipeline](customization/pipelines/using_a_predefined_handler.md#modifying-the-default-pipeline)
|
||||
+ [Constructing the pipeline from scratch](customization/pipelines/using_a_predefined_handler.md#constructing-the-pipeline-from-scratch)
|
||||
+ [Implementing a handler](customization/pipelines/implementing_a_handler.md)
|
||||
+ [Implementing custom API methods](customization/implementing_custom_api_methods.md)
|
||||
* [Troubleshooting](troubleshooting.md)
|
||||
* [PHPDoc](https://retailcrm.github.io/api-client-php/)
|
48
doc/structure.md
Normal file
48
doc/structure.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
## Client structure
|
||||
|
||||
The client is separated into several resource groups, all of which are accessible through the Client's public properties. Each resource group is responsible for the corresponding API section. It is much easier to understand how the Client works by learning its design principles.
|
||||
|
||||
### Design principles
|
||||
|
||||
The general principles of the Client design are:
|
||||
1. The Client itself doesn't contain any API methods implementations.
|
||||
1. ResourceGroups implements all API methods.
|
||||
1. Every method can accept from zero to several arguments.
|
||||
1. URI path arguments (not the query string ones) are always passed as separate method arguments.
|
||||
1. Query parameters are always passed in the form of the Request model. The same principle applies to the POST form data.
|
||||
1. If the Request contains only one parameter - it'll probably accept it through the constructor. This doesn't apply to the filter requests, which don't have constructors at all.
|
||||
1. All DTO's you'll ever need can be found in the `RetailCrm\Api\Model` namespace.
|
||||
1. Every method has an example of the usage in its DocBlock. Yes, all of them. You can learn how to use any of the methods just by looking at the DocBlock help tooltip provided by your IDE. If it's not provided by your IDE - you'll need to look up how to configure it. It'll ease your work with this (and any other) library a lot.
|
||||
|
||||
### Resource groups
|
||||
|
||||
The resource groups list into which client is separated:
|
||||
|
||||
* `api`
|
||||
* `costs`
|
||||
* `customFields`
|
||||
* `customers`
|
||||
* `customersCorporate`
|
||||
* `delivery`
|
||||
* `features`
|
||||
* `files`
|
||||
* `integration`
|
||||
* `loyalty`
|
||||
* `notifications`
|
||||
* `orders`
|
||||
* `packs`
|
||||
* `payments`
|
||||
* `references`
|
||||
* `segments`
|
||||
* `settings`
|
||||
* `store`
|
||||
* `tasks`
|
||||
* `telephony`
|
||||
* `users`
|
||||
* `verification`
|
||||
* `statistics`
|
||||
* `webAnalytics`
|
||||
|
||||
There is also a special `customMethods` group that is used for custom API methods. Each group except this one implements corresponding API documentation block:
|
||||
* [English](https://docs.retailcrm.pro/Developers/API/APIVersions/APIv5)
|
||||
* [Русский](https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5)
|
59
doc/troubleshooting.md
Normal file
59
doc/troubleshooting.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
### I've got error that looks like this: `Cannot deserialize body: Type "RetailCrm\Api\Model\Response\Api\ApiVersionsResponse" is not known.`
|
||||
|
||||
Run this command to fix the problem:
|
||||
|
||||
```sh
|
||||
composer compile --all
|
||||
```
|
||||
|
||||
The details about that error can be found in the [documentation](compilation_prompt.md).
|
||||
|
||||
### I've got "No Message Factories" error.
|
||||
|
||||
This means that PSR-17 implementation you have is not supported by the service discovery, therefore, API client cannot find proper factories.
|
||||
In order to use your own PSR-17 implementation you need to use `ClientBuilder`. Also, you can just install supported implementation which is
|
||||
much easier:
|
||||
```sh
|
||||
composer require nyholm/psr7
|
||||
```
|
||||
|
||||
### I've got `Http\Discovery\Exception\DiscoveryFailedException` or any other error with message like "`Could not find resource using any discovery strategy`".
|
||||
|
||||
That's because you don't have any supported PSR-18, PSR-7 or PSR-17 implementation available. This usually happens if you do have any implementation for those
|
||||
standards, but it's not supported by service discovery. You can fix this easily by installing supported implementations. We recommend using `symfony/http-client`
|
||||
and `nyholm/psr7`. Install those using this command:
|
||||
```sh
|
||||
composer require nyholm/psr7 symfony/http-client
|
||||
```
|
||||
|
||||
Alternatively, you can use `ClientBuilder` which allows you to pass your own HTTP client and message & URI factories.
|
||||
|
||||
### There are too many available exceptions! How do I catch them all?
|
||||
|
||||
Every exception in the library implements either `ApiExceptionInterface` or `ClientExceptionInterface`. First will be thrown in case of any
|
||||
errors from the API, and the second will be thrown in case of any problems with the client or network itself. Concrete exception types are meant
|
||||
to be used to determine what exactly gone wrong while sending a request.
|
||||
|
||||
Also, you can use PSR-14 compatible event dispatcher to handle some exceptions globally. The Client will send `FailureRequestEvent` in case of any exceptions.
|
||||
You can call `FailureRequestEvent::suppressThrow()` to prevent client from throwing an exception.
|
||||
|
||||
### I can't test my app in the CI because Composer fails while installing dependencies.
|
||||
|
||||
That's because you should explicitly allow code generation for the library. Otherwise, Composer will ask you to run compilation task at runtime
|
||||
which is not possible in the CI since it lacks the support for interactive input.
|
||||
|
||||
You can allow code generation tasks without interactive approval. Just add parameters from the JSON below to
|
||||
the `"extra"` key of your project's `composer.json` file.
|
||||
```json
|
||||
{
|
||||
"compile-mode": "whitelist",
|
||||
"compile-whitelist": ["retailcrm/api-client-php"]
|
||||
}
|
||||
```
|
||||
|
||||
### How can I choose proper DTO class for my request?
|
||||
|
||||
Request DTO's can be found in the `RetailCrm\Api\Model\Request` namespace. They are separated by the API resource groups.
|
||||
For example, requests for interaction with customer entities can be found in the `RetailCrm\Api\Model\Request\Customers` namespace
|
||||
and requests for operations with the orders can be found in the `RetailCrm\Api\Model\Request\Orders` namespace. Every request method
|
||||
defines it's input and output types. Also, you can choose correct type for child entities by looking at type annotations.
|
52
doc/usage/error_handling.md
Normal file
52
doc/usage/error_handling.md
Normal file
|
@ -0,0 +1,52 @@
|
|||
## Error handling
|
||||
|
||||
The API Client exceptions are hierarchical. It means that groups of exceptions can be caught using more generic exception types.
|
||||
Let's take a look at the exceptions hierarchy:
|
||||
|
||||
- `ApiException` is an abstraction and will never be thrown by itself.
|
||||
- `AccessDeniedException` is thrown if the `Access denied.` error string is received from the API.
|
||||
- `AccountDoesNotExistException` is thrown if the RetailCRM account with a specified API URL does not exist.
|
||||
- `ApiErrorException` is thrown in case of any generic API error which is not recognized as a more specific type.
|
||||
- `InvalidCredentialsException` is thrown if the provided API key is not correct.
|
||||
- `MissingCredentialsException` is thrown if no API key was provided (this can happen if you initialize the Client without an authenticator).
|
||||
- `MissingParameterException` is thrown if `Parameter '*' is missing` error is returned from the API (`*` is any parameter name).
|
||||
- `ValidationException` is thrown if request validation is failed on the server-side (incorrect request data).
|
||||
- `ClientException` is an abstraction and will never be thrown by itself.
|
||||
- `BuilderException` is thrown by any builder if something goes wrong.
|
||||
- `HandlerException` is thrown by the handlers in the request / response processing chain if they encountered an error.
|
||||
- `HttpClientException` is thrown if underlying HTTP client throws an exception.
|
||||
|
||||
`ApiException` implements `ApiExceptionInterface` and `ClientException` implements `ClientExceptionInterface`.
|
||||
It means that you can catch any exception in the `ApiException` leaf using `ApiExceptionInterface` as an exception type.
|
||||
Also, you can catch any exception in the `ClientException` leaf using the `ClientExceptionInterface` type.
|
||||
|
||||
`ClientExceptionInterface` and `ApiExceptionInterface` implements `__toString()`. The first one will return a string with an error
|
||||
message and stack trace and the second one will print out the error message, stack trace, and error response data.
|
||||
|
||||
Let's take a look at the example:
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
try {
|
||||
$apiVersions = $client->api->apiVersions();
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception;
|
||||
return;
|
||||
}
|
||||
|
||||
echo 'Available API versions: ' . implode(', ', $apiVersions->versions);
|
||||
```
|
||||
|
||||
All Client exceptions can be found in the `RetailCrm\Api\Exception` namespace. `ApiException` children reside in the `RetailCrm\Api\Exception\Api` namespace,
|
||||
and `ClientException` children reside at the `RetailCrm\Api\Exception\Client` namespace.
|
||||
|
||||
It is recommended to look at the [complete error handling example](examples/complete_error_handling_example.md).
|
||||
The most useful case would be the error message generation for the client in your app, which can look like this:
|
||||
- `InvalidCredentialsException` - show the `Invalid API key` message.
|
||||
- `AccountDoesNotExistException` - show the `Incorrect RetailCRM URL` or `This RetailCRM instance does not exist` message.
|
||||
- `AccessDeniedException` - show the `Please enable the /api/v5/... method for this key` message.
|
||||
- and so on.
|
105
doc/usage/event_handing.md
Normal file
105
doc/usage/event_handing.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
## Events
|
||||
|
||||
You can use a PSR-14 compatible event dispatcher to receive events from the client.
|
||||
It may be useful if you want to process certain events with the same logic without duplicating calls to such code.
|
||||
These events are provided by the library:
|
||||
- `RetailCrm\Api\Event\SuccessRequestEvent` will be dispatched if the request was successful.
|
||||
- `RetailCrm\Api\Event\FailureRequestEvent` will be dispatched if the request was not successful. It won't be dispatched if the request cannot be formed at all.
|
||||
|
||||
Event API documentation can be found here:
|
||||
* [`FailureRequestEvent`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Event-FailureRequestEvent.html)
|
||||
* [`SuccessRequestEvent`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Event-SuccessRequestEvent.html)
|
||||
|
||||
Here is an example of event handling with Symfony. We're using an empty Symfony 5.x project with annotations routing. `IndexController`
|
||||
outputs `/api/credentials` response without any changes. We'll handle all exceptions inside this event listener which will allow us
|
||||
to call the method without any `try...catch` blocks.
|
||||
|
||||
**config/services.yml**
|
||||
```yaml
|
||||
services:
|
||||
# ClientFactory definition.
|
||||
RetailCrm\Api\Interfaces\ClientFactoryInterface:
|
||||
class: 'RetailCrm\Api\Factory\ClientFactory'
|
||||
calls:
|
||||
- setCacheDir: ['%kernel.cache_dir%']
|
||||
- setEventDispatcher: ['@event_dispatcher']
|
||||
|
||||
# Event listener definition. This listener will suppress exceptions and log them.
|
||||
App\EventListener\FailureRequestListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: RetailCrm\Api\Event\FailureRequestEvent, method: onRequestFailure }
|
||||
```
|
||||
|
||||
**src/EventListener/FailureRequestListener.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RetailCrm\Api\Event\FailureRequestEvent;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
|
||||
class FailureRequestListener
|
||||
{
|
||||
/** @var \Psr\Log\LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function onRequestFailure(FailureRequestEvent $event): void
|
||||
{
|
||||
$exception = $event->getException();
|
||||
|
||||
if ($exception instanceof ApiExceptionInterface) {
|
||||
$event->suppressThrow();
|
||||
$this->logger->error(sprintf(
|
||||
'CRM URL "%s", API error: %s',
|
||||
$event->getApiUrl(),
|
||||
(string) $exception
|
||||
));
|
||||
}
|
||||
|
||||
if ($exception instanceof ClientExceptionInterface) {
|
||||
$event->suppressThrow();
|
||||
$this->logger->error(sprintf(
|
||||
'CRM URL "%s", client error: %s, trace: %s',
|
||||
$event->getApiUrl(),
|
||||
$exception->getMessage(),
|
||||
$exception->getTraceAsString()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**src/Controller/IndexController.php**
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientFactoryInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
class IndexController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @Route("/", name="index")
|
||||
*/
|
||||
public function index(ClientFactoryInterface $clientFactory): Response
|
||||
{
|
||||
$client = $clientFactory->createClient('https://test3487687.retailcrm.pro', 'key');
|
||||
$credentials = $client->api->credentials();
|
||||
|
||||
// Will print out empty model because https://test3487687.retailcrm.pro account does not exist.
|
||||
return $this->json($credentials);
|
||||
}
|
||||
}
|
||||
```
|
115
doc/usage/examples/complete_error_handling_example.md
Normal file
115
doc/usage/examples/complete_error_handling_example.md
Normal file
|
@ -0,0 +1,115 @@
|
|||
Here is a complete example of the Client usage with error handling. It fetches the filtered orders data and prints it out.
|
||||
|
||||
**Note:** it's not recommended using this example to process all API client errors in your application. It will clutter your code too much.
|
||||
There are other options for exception processing. You can [match more generic exceptions in the hierarchy](../error_handling.md) or use
|
||||
[events](../event_handing.md) to process exceptions.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface as PsrClientExceptionInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use Psr\Http\Client\RequestExceptionInterface;
|
||||
use RetailCrm\Api\Exception\Api\AccessDeniedException;
|
||||
use RetailCrm\Api\Exception\Api\AccountDoesNotExistException;
|
||||
use RetailCrm\Api\Exception\Api\ApiErrorException;
|
||||
use RetailCrm\Api\Exception\Api\InvalidCredentialsException;
|
||||
use RetailCrm\Api\Exception\Api\MissingCredentialsException;
|
||||
use RetailCrm\Api\Exception\Api\MissingParameterException;
|
||||
use RetailCrm\Api\Exception\Api\ValidationException;
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Exception\Client\HttpClientException;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
||||
use RetailCrm\Api\Model\Filter\Orders\OrderFilter;
|
||||
use RetailCrm\Api\Model\Request\Orders\OrdersRequest;
|
||||
use Throwable;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
$request = new OrdersRequest();
|
||||
$request->filter = new OrderFilter();
|
||||
$request->filter->ids = [7141];
|
||||
|
||||
try {
|
||||
$response = $client->orders->list($request);
|
||||
} catch (HandlerException $exception) {
|
||||
echo 'Error while trying to prepare request: ' . $exception->getMessage();
|
||||
exit(-1);
|
||||
} catch (HttpClientException $exception) {
|
||||
$prefix = 'Unknown error';
|
||||
|
||||
if ($exception->getPrevious() instanceof NetworkExceptionInterface) {
|
||||
$prefix = 'Network error';
|
||||
}
|
||||
|
||||
if ($exception->getPrevious() instanceof RequestExceptionInterface) {
|
||||
$prefix = 'Invalid request';
|
||||
}
|
||||
|
||||
if ($exception->getPrevious() instanceof PsrClientExceptionInterface) {
|
||||
$prefix = 'HTTP client exception';
|
||||
}
|
||||
|
||||
echo $prefix . ': ' . $exception->getMessage();
|
||||
|
||||
exit(-1);
|
||||
} catch (
|
||||
InvalidCredentialsException |
|
||||
AccessDeniedException |
|
||||
AccountDoesNotExistException |
|
||||
MissingCredentialsException |
|
||||
MissingParameterException $exception
|
||||
) {
|
||||
echo $exception->getMessage();
|
||||
exit(-1);
|
||||
} catch (ValidationException $exception) {
|
||||
echo 'Errors in fields:' . PHP_EOL;
|
||||
|
||||
foreach ($exception->getErrorResponse()->errors as $field => $error) {
|
||||
printf(" - %s: %s\n", $field, $error);
|
||||
}
|
||||
|
||||
exit(-1);
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiException and ClientException implements __toString() method
|
||||
exit(-1);
|
||||
} catch (Throwable $throwable) {
|
||||
echo 'Unknown runtime exception: ' . $throwable->getMessage() . PHP_EOL;
|
||||
echo $throwable->getTraceAsString();
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
foreach ($response->orders as $order) {
|
||||
printf("Order ID: %d\n", $order->id);
|
||||
printf("First name: %s\n", $order->firstName);
|
||||
printf("Last name: %s\n", $order->lastName);
|
||||
printf("Patronymic: %s\n", $order->patronymic);
|
||||
printf("Phone #1: %s\n", $order->phone);
|
||||
printf("Phone #2: %s\n", $order->additionalPhone);
|
||||
printf("E-Mail: %s\n", $order->email);
|
||||
|
||||
if ($order->customer instanceof CustomerCorporate) {
|
||||
echo "Customer type: corporate\n";
|
||||
} else {
|
||||
echo "Customer type: individual\n";
|
||||
}
|
||||
|
||||
foreach ($order->items as $item) {
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Product name: %s\n", $item->productName);
|
||||
printf("Quantity: %d\n", $item->quantity);
|
||||
printf("Initial price: %f\n", $item->initialPrice);
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Discount: %f\n", $order->discountManualAmount);
|
||||
printf("Total: %f\n", $order->totalSumm);
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
```
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category PaginatedRequestIterator
|
||||
*/
|
||||
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestInterface;
|
||||
use RetailCrm\Api\Model\Response\AbstractPaginatedResponse;
|
||||
|
||||
/**
|
||||
* Class PaginatedRequestIterator
|
||||
*
|
||||
* @category PaginatedRequestIterator
|
||||
* @template T
|
||||
* @implements Iterator<int|string, T>
|
||||
*/
|
||||
class PaginatedRequestIterator implements Iterator
|
||||
{
|
||||
/** @var \RetailCrm\Api\Client */
|
||||
private $client;
|
||||
|
||||
/** @var array<int|string, mixed> */
|
||||
private $items = [];
|
||||
|
||||
/** @var int */
|
||||
private $position = 0;
|
||||
|
||||
/** @var int */
|
||||
private $seek = 0;
|
||||
|
||||
/** @var int */
|
||||
private $currentPage = 1;
|
||||
|
||||
/** @var bool */
|
||||
private $fetchFailed = false;
|
||||
|
||||
/** @var \Throwable|null */
|
||||
private $error;
|
||||
|
||||
/** @var bool */
|
||||
private $finalized = false;
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\RequestInterface */
|
||||
private $request;
|
||||
|
||||
/** @var callable */
|
||||
private $nextPageFunc;
|
||||
|
||||
/** @var callable */
|
||||
private $sendResponseFunc;
|
||||
|
||||
/** @var callable */
|
||||
private $extractBatchFunc;
|
||||
|
||||
/**
|
||||
* PaginatedRequestIterator constructor.
|
||||
*
|
||||
* @param \RetailCrm\Api\Client $client
|
||||
*/
|
||||
public function __construct(Client $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* This request will be used in every iteration.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestInterface $request
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRequest(RequestInterface $request): self
|
||||
{
|
||||
$this->request = $request;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should modify provided request which will be used in the next iteration.
|
||||
*
|
||||
* These params will be supplied in this exact order:
|
||||
* - The request instance.
|
||||
* - The response instance.
|
||||
* - Next page number.
|
||||
*
|
||||
* @param callable $nextPageFunc
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setNextPageFunc(callable $nextPageFunc): self
|
||||
{
|
||||
$this->nextPageFunc = $nextPageFunc;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should send the request and return the response.
|
||||
*
|
||||
* These params will be supplied in this exact order:
|
||||
* - The Client instance.
|
||||
* - The request instance.
|
||||
*
|
||||
* @param callable $sendResponseFunc
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSendResponseFunc(callable $sendResponseFunc): self
|
||||
{
|
||||
$this->sendResponseFunc = $sendResponseFunc;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function should extract the batch of objects from the response.
|
||||
*
|
||||
* These params will be supplied in this exact order:
|
||||
* - The response instance.
|
||||
*
|
||||
* @param callable $extractBatchFunc
|
||||
*
|
||||
* @return PaginatedRequestIterator
|
||||
*/
|
||||
public function setExtractBatchFunc(callable $extractBatchFunc): PaginatedRequestIterator
|
||||
{
|
||||
$this->extractBatchFunc = $extractBatchFunc;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @return mixed
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->items[$this->getPosition()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
++$this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
if (empty($this->items) || !array_key_exists($this->getPosition(), $this->items)) {
|
||||
$this->fetchChunk();
|
||||
}
|
||||
|
||||
return isset($this->items[$this->getPosition()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->items = [];
|
||||
$this->position = 0;
|
||||
$this->seek = 0;
|
||||
$this->currentPage = 1;
|
||||
$this->error = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFetchFailed(): bool
|
||||
{
|
||||
return $this->fetchFailed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Throwable|null
|
||||
*/
|
||||
public function getError(): ?Throwable
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function getPosition(): int
|
||||
{
|
||||
return $this->position - $this->seek;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetchChunk obtains chunk of data
|
||||
*/
|
||||
private function fetchChunk(): void
|
||||
{
|
||||
if ($this->finalized || $this->fetchFailed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->currentPage > 1) {
|
||||
time_nanosleep(0, 100000000);
|
||||
}
|
||||
|
||||
$response = call_user_func($this->sendResponseFunc, $this->client, $this->request);
|
||||
|
||||
if ($response instanceof AbstractPaginatedResponse) {
|
||||
$batch = call_user_func($this->extractBatchFunc, $response);
|
||||
|
||||
if (empty($batch)) {
|
||||
$this->items = [];
|
||||
$this->fetchFailed = true;
|
||||
$this->error = new HandlerException('Received an empty response');
|
||||
} else {
|
||||
$this->seek += count($this->items);
|
||||
$this->items = array_values($batch);
|
||||
|
||||
if ($this->currentPage < $response->pagination->totalPageCount) {
|
||||
call_user_func($this->nextPageFunc, $this->request, $response, ++$this->currentPage);
|
||||
} else {
|
||||
$this->finalized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
if ($exception instanceof ApiExceptionInterface && 503 === $exception->getStatusCode()) {
|
||||
time_nanosleep(1, 0);
|
||||
$this->fetchChunk();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fetchFailed = true;
|
||||
$this->error = $exception;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Enum\PaginationLimit;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Filter\Customers\CustomerHistoryFilter;
|
||||
use RetailCrm\Api\Model\Request\Customers\CustomersHistoryRequest;
|
||||
use RetailCrm\Api\Model\Response\Customers\CustomersHistoryResponse;
|
||||
|
||||
require __DIR__ . '/../../../../vendor/autoload.php';
|
||||
require __DIR__ . '/PaginatedRequestIterator.php';
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro/', 'apiKey');
|
||||
$request = new CustomersHistoryRequest();
|
||||
$request->filter = new CustomerHistoryFilter();
|
||||
$request->filter->startDate = (new DateTime())->sub(new DateInterval('P7D')); // 7 days
|
||||
$request->limit = PaginationLimit::LIMIT_100;
|
||||
|
||||
$paginator = (new PaginatedRequestIterator($client))
|
||||
->setRequest($request)
|
||||
->setNextPageFunc(static function (CustomersHistoryRequest $request, CustomersHistoryResponse $response, int $page) {
|
||||
$request->filter->startDate = null;
|
||||
$request->filter->sinceId = end($response->history)->id;
|
||||
})
|
||||
->setExtractBatchFunc(static function (CustomersHistoryResponse $response) {
|
||||
return $response->history;
|
||||
})
|
||||
->setSendResponseFunc(static function (Client $client, CustomersHistoryRequest $request) {
|
||||
return $client->customers->history($request);
|
||||
});
|
||||
|
||||
/** @var \RetailCrm\Api\Model\Entity\Customers\Customer $customer */
|
||||
foreach ($paginator as $historyEntry) {
|
||||
print_r($historyEntry);
|
||||
}
|
||||
|
||||
if ($paginator->isFetchFailed()) {
|
||||
echo $paginator->getError();
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Enum\PaginationLimit;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Entity\Customers\CustomerPhone;
|
||||
use RetailCrm\Api\Model\Request\Customers\CustomersRequest;
|
||||
use RetailCrm\Api\Model\Response\Customers\CustomersResponse;
|
||||
|
||||
require __DIR__ . '/../../../../vendor/autoload.php';
|
||||
require __DIR__ . '/PaginatedRequestIterator.php';
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro/', 'apiKey');
|
||||
$request = new CustomersRequest();
|
||||
$request->limit = PaginationLimit::LIMIT_100;
|
||||
$request->page = 1;
|
||||
|
||||
$paginator = (new PaginatedRequestIterator($client))
|
||||
->setRequest($request)
|
||||
->setNextPageFunc(static function (CustomersRequest $request, CustomersResponse $response, int $page) {
|
||||
$request->page = $page;
|
||||
})
|
||||
->setExtractBatchFunc(static function (CustomersResponse $response) {
|
||||
return $response->customers;
|
||||
})
|
||||
->setSendResponseFunc(static function (Client $client, CustomersRequest $request) {
|
||||
return $client->customers->list($request);
|
||||
});
|
||||
|
||||
/** @var \RetailCrm\Api\Model\Entity\Customers\Customer $customer */
|
||||
foreach ($paginator as $customer) {
|
||||
printf(
|
||||
'%d: %s %s %s <%s> (%s) %s',
|
||||
$customer->id,
|
||||
$customer->firstName,
|
||||
$customer->lastName,
|
||||
$customer->patronymic,
|
||||
implode(', ', array_map(static function (CustomerPhone $phone) {
|
||||
return $phone->number;
|
||||
}, $customer->phones)),
|
||||
$customer->email, PHP_EOL
|
||||
);
|
||||
}
|
||||
|
||||
if ($paginator->isFetchFailed()) {
|
||||
echo $paginator->getError();
|
||||
}
|
8
doc/usage/examples/complex_pagination/index.md
Normal file
8
doc/usage/examples/complex_pagination/index.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
Here are some complex examples of pagination handling. In this case, we're using a special component that handles the pagination for us.
|
||||
As a result, we can just iterate over batches of the data using `foreach`.
|
||||
|
||||
More than that, this component automatically handles rate limit exceptions.
|
||||
|
||||
* [Fetching customers](example_customers_list.php)
|
||||
* [Fetching customers history](example_customers_history.php)
|
||||
* [Pagination component (`PaginatedRequestIterator`)](PaginatedRequestIterator.php)
|
104
doc/usage/examples/create_order.md
Normal file
104
doc/usage/examples/create_order.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
That's how you can create a new order:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Enum\CountryCodeIso3166;
|
||||
use RetailCrm\Api\Enum\Customers\CustomerType;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Delivery\OrderDeliveryAddress;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Delivery\SerializedOrderDelivery;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Items\Offer;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Items\OrderProduct;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Items\PriceType;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Items\Unit;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Order;
|
||||
use RetailCrm\Api\Model\Entity\Orders\Payment;
|
||||
use RetailCrm\Api\Model\Entity\Orders\SerializedRelationCustomer;
|
||||
use RetailCrm\Api\Model\Request\Orders\OrdersCreateRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
$request = new OrdersCreateRequest();
|
||||
$order = new Order();
|
||||
$payment = new Payment();
|
||||
$delivery = new SerializedOrderDelivery();
|
||||
$deliveryAddress = new OrderDeliveryAddress();
|
||||
$offer = new Offer();
|
||||
$item = new OrderProduct();
|
||||
|
||||
$payment->type = 'bank-card';
|
||||
$payment->status = 'paid';
|
||||
$payment->amount = 1000;
|
||||
$payment->paidAt = new DateTime();
|
||||
|
||||
$deliveryAddress->index = '344001';
|
||||
$deliveryAddress->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION;
|
||||
$deliveryAddress->region = 'Region';
|
||||
$deliveryAddress->city = 'City';
|
||||
$deliveryAddress->street = 'Street';
|
||||
$deliveryAddress->building = '10';
|
||||
|
||||
$delivery->address = $deliveryAddress;
|
||||
$delivery->cost = 0;
|
||||
$delivery->netCost = 0;
|
||||
|
||||
$offer->name = 'Offer №1445123';
|
||||
$offer->displayName = 'Offer №1445123';
|
||||
$offer->xmlId = 'tGunLo27jlPGmbA8BrHxY2';
|
||||
$offer->article = '14451445-14451445';
|
||||
$offer->unit = new Unit('796', 'Piece', 'pcs');
|
||||
|
||||
$item->offer = $offer;
|
||||
$item->priceType = new PriceType('base');
|
||||
$item->quantity = 1;
|
||||
$item->purchasePrice = 60;
|
||||
|
||||
$order->delivery = $delivery;
|
||||
$order->items = [$item];
|
||||
$order->payments = [$payment];
|
||||
$order->orderType = 'test';
|
||||
$order->orderMethod = 'phone';
|
||||
$order->countryIso = CountryCodeIso3166::RUSSIAN_FEDERATION;
|
||||
$order->firstName = 'Test';
|
||||
$order->lastName = 'User';
|
||||
$order->patronymic = 'Patronymic';
|
||||
$order->phone = '89003005069';
|
||||
$order->email = 'testuser12345678901@example.com';
|
||||
$order->managerId = 28;
|
||||
$order->customer = SerializedRelationCustomer::withIdAndType(
|
||||
4924,
|
||||
CustomerType::CUSTOMER
|
||||
);
|
||||
$order->status = 'assembling';
|
||||
$order->statusComment = 'Assembling order';
|
||||
$order->weight = 1000;
|
||||
$order->shipmentStore = 'main12';
|
||||
$order->shipmentDate = (new DateTime())->add(new DateInterval('P7D'));
|
||||
$order->shipped = false;
|
||||
$order->customFields = [
|
||||
"galka" => false,
|
||||
"test_number" => 0,
|
||||
"otpravit_dozakaz" => false,
|
||||
];
|
||||
|
||||
$request->order = $order;
|
||||
$request->site = 'moysklad';
|
||||
|
||||
try {
|
||||
$response = $client->orders->create($request);
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
printf(
|
||||
'Created order id = %d with the following data: %s',
|
||||
$response->id,
|
||||
print_r($response->order, true)
|
||||
);
|
||||
```
|
||||
|
||||
|
50
doc/usage/examples/fetch_orders.md
Normal file
50
doc/usage/examples/fetch_orders.md
Normal file
|
@ -0,0 +1,50 @@
|
|||
That's how you can fetch the orders list:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Model\Entity\CustomersCorporate\CustomerCorporate;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
|
||||
try {
|
||||
$response = $client->orders->list();
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
foreach ($response->orders as $order) {
|
||||
printf("Order ID: %d\n", $order->id);
|
||||
printf("First name: %s\n", $order->firstName);
|
||||
printf("Last name: %s\n", $order->lastName);
|
||||
printf("Patronymic: %s\n", $order->patronymic);
|
||||
printf("Phone #1: %s\n", $order->phone);
|
||||
printf("Phone #2: %s\n", $order->additionalPhone);
|
||||
printf("E-Mail: %s\n", $order->email);
|
||||
|
||||
if ($order->customer instanceof CustomerCorporate) {
|
||||
echo "Customer type: corporate\n";
|
||||
} else {
|
||||
echo "Customer type: individual\n";
|
||||
}
|
||||
|
||||
foreach ($order->items as $item) {
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Product name: %s\n", $item->productName);
|
||||
printf("Quantity: %d\n", $item->quantity);
|
||||
printf("Initial price: %f\n", $item->initialPrice);
|
||||
}
|
||||
|
||||
echo PHP_EOL;
|
||||
|
||||
printf("Discount: %f\n", $order->discountManualAmount);
|
||||
printf("Total: %f\n", $order->totalSumm);
|
||||
|
||||
echo PHP_EOL;
|
||||
}
|
||||
```
|
7
doc/usage/examples/index.md
Normal file
7
doc/usage/examples/index.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
## Examples
|
||||
|
||||
* [How to create an order](create_order.md)
|
||||
* [How to receive the list of orders](fetch_orders.md)
|
||||
* [How to handle all Client's exceptions](complete_error_handling_example.md)
|
||||
* [Fetching orders history](orders_history.md)
|
||||
* [Complex pagination example](complex_pagination/index.md)
|
34
doc/usage/examples/orders_history.md
Normal file
34
doc/usage/examples/orders_history.md
Normal file
|
@ -0,0 +1,34 @@
|
|||
In this example we will fetch all history entries for 1 month from the API.
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Enum\PaginationLimit;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
use RetailCrm\Api\Model\Filter\Orders\OrderHistoryFilterV4Type;
|
||||
use RetailCrm\Api\Model\Request\Orders\OrdersHistoryRequest;
|
||||
|
||||
$history = [];
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'apiKey');
|
||||
$request = new OrdersHistoryRequest();
|
||||
$request->filter = new OrderHistoryFilterV4Type();
|
||||
$request->limit = PaginationLimit::LIMIT_100;
|
||||
$request->filter->startDate = (new DateTime())->sub(new DateInterval('P1M')); // History for 1 month by default.
|
||||
|
||||
do {
|
||||
time_nanosleep(0, 100000000); // 10 requests per second
|
||||
|
||||
try {
|
||||
$response = $client->orders->history($request);
|
||||
} catch (ClientExceptionInterface | ApiExceptionInterface $exception) {
|
||||
echo $exception;
|
||||
break;
|
||||
}
|
||||
|
||||
$history = array_merge($history, $response->history);
|
||||
$request->filter->startDate = null;
|
||||
$request->filter->sinceId = end($response->history)->id;
|
||||
} while ($response->pagination->currentPage < $response->pagination->totalPageCount);
|
||||
|
||||
// Here you can process all history entries in the `$history` variable.
|
||||
```
|
255
doc/usage/instantiation.md
Normal file
255
doc/usage/instantiation.md
Normal file
|
@ -0,0 +1,255 @@
|
|||
## Instantiation
|
||||
|
||||
There are several ways to instantiate the Client. Let's take look at them: from the simplest one to the most complex.
|
||||
|
||||
* [Instantiation via `SimpleClientFactory`](#instantiation-via-simpleclientfactory)
|
||||
* [Instantiation via `ClientFactory`](#instantiation-via-clientfactory)
|
||||
+ [Simple example](#simple-example)
|
||||
+ [Abstract example](#abstract-example)
|
||||
+ [Real-world example (Symfony)](#real-world-example-symfony)
|
||||
* [Instantiation via `ClientBuilder`](#instantiation-via-clientbuilder)
|
||||
|
||||
### Instantiation via `SimpleClientFactory`
|
||||
|
||||
It's the easiest way to instantiate an API client. You can use this method if you just want to use API client without
|
||||
thinking much about integration with the app or framework.
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
```
|
||||
|
||||
That's it. You can use `$client` to make requests. However, even this simple method allows you to pass the `CacheItemPoolInterface` instance
|
||||
(`symfony/cache` is being used by default) or path to the cache directory. The cache is being used by the client to store
|
||||
annotations which omits the necessity to parse them each time you send a request.
|
||||
|
||||
Example with the `CacheItemPoolInterface` instance (we're using Redis as cache here):
|
||||
|
||||
```php
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
use RetailCrm\Api\Enum\CacheDirectories;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
|
||||
$redis = new Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
|
||||
$client = SimpleClientFactory::createWithCache(
|
||||
'https://test.retailcrm.pro',
|
||||
'key',
|
||||
new RedisAdapter($redis, CacheDirectories::DIR_NAME)
|
||||
);
|
||||
```
|
||||
|
||||
`CacheDirectories::DIR_NAME` is optional here. You can use any other namespace you want. By default, `CacheDirectories::DIR_NAME`
|
||||
will be used as a namespace by default if the cache instance was created by the client automatically.
|
||||
|
||||
It's easier to use `SimpleClientFactory::createWithCacheDir` if you just want to specify your directory for cache instead of
|
||||
default temporary directory:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
|
||||
$client = SimpleClientFactory::createWithCacheDir('https://test.retailcrm.pro', 'key', __DIR__ . '/cache');
|
||||
```
|
||||
|
||||
You can find more details about `SimpleClientFactory` in the documentation:
|
||||
* [`SimpleClientFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-SimpleClientFactory.html)
|
||||
|
||||
### Instantiation via `ClientFactory`
|
||||
|
||||
The main difference between `SimpleClientFactory` and `ClientFactory` is the fact that the second factory is stateful.
|
||||
It allows you to better integrate this client with your application. Consider the following: you may just set all global client
|
||||
dependencies into the `ClientFactory` during the dependency container instantiation, and use the factory after that
|
||||
to instantiate any number of clients you want.
|
||||
|
||||
#### Simple example
|
||||
|
||||
Here's an example of `ClientFactory` usage without any integration with the framework or the app:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\ClientFactory;
|
||||
use League\Event\EventDispatcher;
|
||||
|
||||
$eventDispatcher = new EventDispatcher();
|
||||
$factory = new ClientFactory();
|
||||
$factory
|
||||
->setCacheDir('/tmp/retailcrm_cache')
|
||||
->setEventDispatcher($eventDispatcher);
|
||||
|
||||
$client = $factory->createClient('https://test.retailcrm.pro', 'key');
|
||||
```
|
||||
|
||||
Doesn't look that impressive, right? That's because `ClientFactory` is not supposed to be used just like that.
|
||||
It won't give you any benefits in comparison to `SimpleClientFactory` with such usage.
|
||||
|
||||
Let's take a look at the more complex example. We'll use `league/container` here to demonstrate how you can integrate
|
||||
`ClientFactory` with the DI you're using.
|
||||
|
||||
#### Abstract example
|
||||
|
||||
Let's assume that you have a specific service that should instantiate a Client for some internal purposes.
|
||||
Here it is:
|
||||
|
||||
```php
|
||||
namespace App\Services;
|
||||
|
||||
use RetailCrm\Api\Interfaces\ClientFactoryInterface;
|
||||
use Throwable;
|
||||
|
||||
class ClientFactoryDependentService
|
||||
{
|
||||
/** @var ClientFactoryInterface */
|
||||
private $clientFactory;
|
||||
|
||||
public function __construct(ClientFactoryInterface $clientFactory)
|
||||
{
|
||||
$this->clientFactory = $clientFactory;
|
||||
}
|
||||
|
||||
public function isApiAccessible(string $apiUrl, string $apiKey): bool
|
||||
{
|
||||
try {
|
||||
$client = $this->clientFactory->createClient($apiUrl, $apiKey);
|
||||
$client->api->apiVersions();
|
||||
|
||||
return true;
|
||||
} catch (Throwable $throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And you have a controller which returns a JSON response with the API availability flag. It requires the service above.
|
||||
|
||||
```php
|
||||
namespace App\Controller;
|
||||
|
||||
use Some\Framework\Specific\Annotations\Route;
|
||||
use Some\Framework\Specific\Controller;
|
||||
use Some\Framework\Specific\Response;
|
||||
|
||||
class HealthCheckController extends Controller
|
||||
{
|
||||
/** @var ClientFactoryDependentService */
|
||||
private $service;
|
||||
|
||||
public function __construct(ClientFactoryDependentService $service)
|
||||
{
|
||||
$this->service = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/healthcheck")
|
||||
*/
|
||||
public function healthCheckAction(): Response
|
||||
{
|
||||
return $this->responseJson([
|
||||
'is_api_accessible' => $this->service->isApiAccessible($_GET['apiUrl'], $_GET['apiKey'])
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
During the container configuration you configure the container to instantiate proper `ClientFactory` once. After that
|
||||
you can use the factory in any service you want. Here's an example with the `league/container`:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\ClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ClientFactoryInterface;
|
||||
use League\Container\Container;
|
||||
use League\Event\EventDispatcher;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use App\Services\ClientFactoryDependentService;
|
||||
use App\Controller\HealthCheckController;
|
||||
|
||||
$container = new Container();
|
||||
|
||||
$container->add(CacheItemPoolInterface::class, new FilesystemAdapter('test_app'));
|
||||
$container->add(EventDispatcherInterface::class, EventDispatcher::class);
|
||||
$container->add(ClientFactoryInterface::class, ClientFactory::class)
|
||||
->addMethodCalls([
|
||||
'setCache' => [CacheItemPoolInterface::class],
|
||||
'setEventDispatcher' => [EventDispatcherInterface::class],
|
||||
]);
|
||||
$container->add(ClientFactoryDependentService::class)->addArgument(ClientFactoryInterface::class);
|
||||
$container->add(HealthCheckController::class)->addArgument(ClientFactoryDependentService::class);
|
||||
```
|
||||
|
||||
That's it! Now your controller is using the underlying service to form a response. The service is using `ClientFactory`
|
||||
which is being instantiated only once by the container. `ClientFactory` can be injected into other services as well, and it
|
||||
will be the same instance.
|
||||
|
||||
#### Real-world example (Symfony)
|
||||
|
||||
You can use this exact service definition to instantiate a `ClientFactory` instance:
|
||||
|
||||
```yaml
|
||||
RetailCrm\Api\Interfaces\ClientFactoryInterface:
|
||||
class: 'RetailCrm\Api\Factory\ClientFactory'
|
||||
calls:
|
||||
- setCacheDir: ['%kernel.cache_dir%']
|
||||
- setEventDispatcher: ['@event_dispatcher']
|
||||
```
|
||||
|
||||
That's it. Now you can autowire `ClientFactoryInterface` in your services. It also allows you to mock the factory itself
|
||||
in the tests using `services_test.yml`.
|
||||
|
||||
You can find more details about `ClientFactory` in the documentation:
|
||||
* [`ClientFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-ClientFactory.html)
|
||||
|
||||
### Instantiation via `ClientBuilder`
|
||||
|
||||
`ClientBuilder` is the most powerful, and most complex method of client instantiation. It allows you to replace any client's
|
||||
external dependencies with your own implementations. Here's an example of `ClientBuilder` usage:
|
||||
|
||||
```php
|
||||
use Http\Client\Curl\Client as CurlClient;
|
||||
use League\Event\EventDispatcher;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use RetailCrm\Api\Builder\ClientBuilder;
|
||||
use RetailCrm\Api\Builder\FormEncoderBuilder;
|
||||
use RetailCrm\Api\Component\Transformer\RequestTransformer;
|
||||
use RetailCrm\Api\Component\Transformer\ResponseTransformer;
|
||||
use RetailCrm\Api\Factory\ApiExceptionFactory;
|
||||
use RetailCrm\Api\Factory\RequestPipelineFactory;
|
||||
use RetailCrm\Api\Factory\ResponsePipelineFactory;
|
||||
use RetailCrm\Api\Handler\Request\HeaderAuthenticatorHandler;
|
||||
|
||||
$eventDispatcher = new EventDispatcher();
|
||||
$psr17Factory = new Psr17Factory();
|
||||
$httpClient = new CurlClient();
|
||||
|
||||
$builder = new ClientBuilder();
|
||||
$formEncoder = (new FormEncoderBuilder())->setCacheDir('cache')->build();
|
||||
$client = $builder->setApiUrl('https://test.retailcrm.pro')
|
||||
->setAuthenticatorHandler(new HeaderAuthenticatorHandler('apiKey'))
|
||||
->setFormEncoder($formEncoder)
|
||||
->setHttpClient($httpClient)
|
||||
->setRequestTransformer(new RequestTransformer(
|
||||
RequestPipelineFactory::createDefaultPipeline(
|
||||
$formEncoder,
|
||||
$psr17Factory, // PSR-17 UriFactoryInterface
|
||||
$psr17Factory, // PSR-17 RequestFactoryInterface
|
||||
$psr17Factory // PSR-17 StreamFactoryInterface
|
||||
)
|
||||
))
|
||||
->setResponseTransformer(new ResponseTransformer(
|
||||
ResponsePipelineFactory::createDefaultPipeline(
|
||||
$formEncoder->getSerializer(),
|
||||
new ApiExceptionFactory(),
|
||||
$eventDispatcher
|
||||
)
|
||||
))->build();
|
||||
```
|
||||
|
||||
You can find more details about this in the documentation:
|
||||
* [`ClientBuilder`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Builder-ClientBuilder.html)
|
||||
* [`FormEncoderBuilder`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Builder-FormEncoderBuilder.html)
|
||||
* [`RequestPipelineFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-RequestPipelineFactory.html)
|
||||
* [`ResponsePipelineFactory`](https://retailcrm.github.io/api-client-php/classes/RetailCrm-Api-Factory-ResponsePipelineFactory.html)
|
144
doc/usage/sending_a_request.md
Normal file
144
doc/usage/sending_a_request.md
Normal file
|
@ -0,0 +1,144 @@
|
|||
## Sending a request
|
||||
|
||||
You need three things to send a request:
|
||||
1. [Resource group, DTOs, and method](#choosing-correct-resource-group-dtos-and-method)
|
||||
2. [Send a request](#sending-a-request)
|
||||
3. [Deal with exceptions](#dealing-with-exceptions)
|
||||
|
||||
### Choosing correct resource group, DTOs, and method
|
||||
|
||||
First, take a look at the API itself:
|
||||
* [English](https://docs.retailcrm.pro/Developers/API/APIVersions/APIv5)
|
||||
* [Русский](https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5)
|
||||
|
||||
Choose a method you want to use. Which one is yours depend on the task you want to perform.
|
||||
|
||||
Then take look at the API again. It consists of several blocks, each block is responsible for a specific set of features.
|
||||
The Client itself is also separated to those blocks or resource groups (full list of them can be found [here](../structure.md#resource-groups)).
|
||||
|
||||
Each resource group corresponds to an equal block in the documentation. For example. `customersCorporate` resource group implements methods
|
||||
in the _Corporate customers_ API block (рус. _Корпоративные клиенты_).
|
||||
|
||||
Just look at the method you want to use and pick a proper resource group. That's it. Let's move on to the DTOs.
|
||||
|
||||
Choosing proper DTOs is also easy. Each client method defines expected parameter types. If the method doesn't have any
|
||||
parameters, then the API method is also doesn't require any parameters and can be just called. The most noteworthy would be
|
||||
`/api/api-versions` and `/api/credentials` methods. They can be called from the Client instance like that:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
$client->api->apiVersions();
|
||||
$client->api->credentials();
|
||||
```
|
||||
|
||||
The majority of the methods will require some parameters. Usually it's a request DTO, some path parameters (like customer id)
|
||||
or both. For example, `/api/v5/customers/create` method requires request DTO, `/api/v5/customers/{externalId}` requires
|
||||
customer ID and `/api/v5/customers/{externalId}/edit` requires both.
|
||||
|
||||
The corresponding methods on the client instance are:
|
||||
|
||||
| API method | Client instance method |
|
||||
| ------------------------------------- | ---------------------------- |
|
||||
| `/api/v5/customers/create` | `$client->customers->create` |
|
||||
| `/api/v5/customers/{externalId}` | `$client->customers->get` |
|
||||
| `/api/v5/customers/{externalId}/edit` | `$client->customers->edit` |
|
||||
|
||||
Look at the method definitions (they have also shown by the IDE):
|
||||
|
||||
```php
|
||||
public function create(CustomersCreateRequest $request): IdResponse;
|
||||
public function get($identifier, ?BySiteRequest $request = null): CustomersGetResponse;
|
||||
public function edit($identifier, CustomersEditRequest $request): CustomersEditResponse;
|
||||
```
|
||||
|
||||
Just use types from the type hints and everything will work. The DTO's fields also have type declarations but in the form
|
||||
of the `@var` annotation tags. That's how you can choose the correct type for the DTO fields.
|
||||
|
||||
### Sending a request
|
||||
|
||||
Using information from the previous article you can easily construct a request.
|
||||
|
||||
This request will create a new customer.
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
||||
use RetailCrm\Api\Model\Request\Customers\CustomersCreateRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
$request = new CustomersCreateRequest();
|
||||
|
||||
$request->customer = new Customer();
|
||||
$request->customer->email = 'test@example.com';
|
||||
$request->site = 'site';
|
||||
|
||||
$response = $client->customers->create($request);
|
||||
```
|
||||
|
||||
This one will fetch specific customer from the API by the ID and site.
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Enum\ByIdentifier;use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Request\BySiteRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
$response = $client->customers->get(1, new BySiteRequest(ByIdentifier::ID, 'site'));
|
||||
|
||||
echo $response->customer->email;
|
||||
```
|
||||
|
||||
And this one will edit specific customer:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Enum\ByIdentifier;
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Model\Entity\Customers\Customer;
|
||||
use RetailCrm\Api\Model\Request\Customers\CustomersEditRequest;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
$request = new CustomersEditRequest();
|
||||
|
||||
$request->customer = new Customer();
|
||||
$request->customer->email = 'test@example.com';
|
||||
$request->site = 'site';
|
||||
$request->by = ByIdentifier::ID;
|
||||
|
||||
$response = $client->customers->edit(1, $request);
|
||||
|
||||
echo "Edited customer ID: " . $response->id;
|
||||
```
|
||||
|
||||
### Dealing with exceptions
|
||||
|
||||
What will happen if an API error is returned for the one of requests above? Or the network is not working at all?
|
||||
An exception will happen.
|
||||
|
||||
There are two main exception types:
|
||||
* `RetailCrm\Api\Interfaces\ApiExceptionInterface` is thrown if API returned an error.
|
||||
* `RetailCrm\Api\Interfaces\ClientExceptionInterface` is thrown if the request cannot be sent due to an internal error.
|
||||
|
||||
You can use those like this:
|
||||
|
||||
```php
|
||||
use RetailCrm\Api\Factory\SimpleClientFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\ClientExceptionInterface;
|
||||
|
||||
$client = SimpleClientFactory::createClient('https://test.retailcrm.pro', 'key');
|
||||
|
||||
try {
|
||||
$response = $client->customers->list();
|
||||
} catch (ApiExceptionInterface $exception) {
|
||||
echo $exception;
|
||||
} catch (ClientExceptionInterface $exception) {
|
||||
echo 'Client error: ' . $exception->getMessage();
|
||||
}
|
||||
|
||||
if (isset($response)) {
|
||||
echo "Customers: " . print_r($response->customers, true);
|
||||
}
|
||||
```
|
||||
|
||||
More information about exceptions can be found on the [error handling](error_handling.md) page.
|
15
doc/usage/usage.md
Normal file
15
doc/usage/usage.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
## Usage
|
||||
|
||||
* [Instantiation](instantiation.md)
|
||||
* [Sending a request](sending_a_request.md)
|
||||
* [Choosing correct resource group, DTOs, and method](sending_a_request.md#choosing-correct-resource-group-dtos-and-method)
|
||||
* [Sending a request](sending_a_request.md#sending-a-request)
|
||||
* [Dealing with exceptions](sending_a_request.md#dealing-with-exceptions)
|
||||
* [Error handling](error_handling.md)
|
||||
* [Examples](examples)
|
||||
+ [How to create an order](examples/create_order.md)
|
||||
+ [How to receive the list of orders](examples/fetch_orders.md)
|
||||
+ [How to handle all Client's exceptions](examples/complete_error_handling_example.md)
|
||||
+ [Fetching orders history](examples/orders_history.md)
|
||||
+ [Complex pagination example](examples/complex_pagination/index.md)
|
||||
* [Event handling](event_handing.md)
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace RetailCrm\Exception;
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
*
|
||||
* Class CurlException
|
||||
*
|
||||
* @category RetailCrm
|
||||
* @package RetailCrm
|
||||
* @author RetailCrm <integration@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://www.retailcrm.ru/docs/Developers/ApiVersion3
|
||||
*/
|
||||
class CurlException extends \RuntimeException
|
||||
{
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace RetailCrm\Exception;
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
*
|
||||
* Class InvalidJsonException
|
||||
*
|
||||
* @category RetailCrm
|
||||
* @package RetailCrm
|
||||
* @author RetailCrm <integration@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://www.retailcrm.ru/docs/Developers/ApiVersion3
|
||||
*/
|
||||
class InvalidJsonException extends \DomainException
|
||||
{
|
||||
}
|
|
@ -1,145 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace RetailCrm\Http;
|
||||
|
||||
use RetailCrm\Exception\CurlException;
|
||||
use RetailCrm\Exception\InvalidJsonException;
|
||||
use RetailCrm\Response\ApiResponse;
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
*
|
||||
* HTTP client
|
||||
*
|
||||
* @category RetailCrm
|
||||
* @package RetailCrm
|
||||
* @author RetailCrm <integration@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://www.retailcrm.ru/docs/Developers/ApiVersion3
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
const METHOD_GET = 'GET';
|
||||
const METHOD_POST = 'POST';
|
||||
|
||||
protected $url;
|
||||
protected $defaultParameters;
|
||||
protected $retry;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param string $url api url
|
||||
* @param array $defaultParameters array of parameters
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($url, array $defaultParameters = array())
|
||||
{
|
||||
if (false === stripos($url, 'https://')) {
|
||||
throw new \InvalidArgumentException(
|
||||
'API schema requires HTTPS protocol'
|
||||
);
|
||||
}
|
||||
|
||||
$this->url = $url;
|
||||
$this->defaultParameters = $defaultParameters;
|
||||
$this->retry = 0;
|
||||
$this->curlErrors = array(
|
||||
CURLE_COULDNT_RESOLVE_PROXY,
|
||||
CURLE_COULDNT_RESOLVE_HOST,
|
||||
CURLE_COULDNT_CONNECT,
|
||||
CURLE_OPERATION_TIMEOUTED,
|
||||
CURLE_HTTP_POST_ERROR,
|
||||
CURLE_SSL_CONNECT_ERROR,
|
||||
CURLE_SEND_ERROR,
|
||||
CURLE_RECV_ERROR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make HTTP request
|
||||
*
|
||||
* @param string $path request url
|
||||
* @param string $method (default: 'GET')
|
||||
* @param array $parameters (default: array())
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @throws CurlException
|
||||
* @throws InvalidJsonException
|
||||
*
|
||||
* @return ApiResponse
|
||||
*/
|
||||
public function makeRequest(
|
||||
$path,
|
||||
$method,
|
||||
array $parameters = array()
|
||||
) {
|
||||
$allowedMethods = array(self::METHOD_GET, self::METHOD_POST);
|
||||
|
||||
if (!in_array($method, $allowedMethods, false)) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf(
|
||||
'Method "%s" is not valid. Allowed methods are %s',
|
||||
$method,
|
||||
implode(', ', $allowedMethods)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$parameters = array_merge($this->defaultParameters, $parameters);
|
||||
|
||||
$url = $this->url . $path;
|
||||
|
||||
if (self::METHOD_GET === $method && count($parameters)) {
|
||||
$url .= '?' . http_build_query($parameters, '', '&');
|
||||
}
|
||||
|
||||
$curlHandler = curl_init();
|
||||
curl_setopt($curlHandler, CURLOPT_URL, $url);
|
||||
curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($curlHandler, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt($curlHandler, CURLOPT_FAILONERROR, false);
|
||||
curl_setopt($curlHandler, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($curlHandler, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($curlHandler, CURLOPT_TIMEOUT, 30);
|
||||
curl_setopt($curlHandler, CURLOPT_CONNECTTIMEOUT, 30);
|
||||
|
||||
if (self::METHOD_POST === $method) {
|
||||
curl_setopt($curlHandler, CURLOPT_POST, true);
|
||||
curl_setopt($curlHandler, CURLOPT_POSTFIELDS, $parameters);
|
||||
}
|
||||
|
||||
$responseBody = curl_exec($curlHandler);
|
||||
$statusCode = curl_getinfo($curlHandler, CURLINFO_HTTP_CODE);
|
||||
$errno = curl_errno($curlHandler);
|
||||
$error = curl_error($curlHandler);
|
||||
|
||||
curl_close($curlHandler);
|
||||
|
||||
if ($errno && in_array($errno, $this->curlErrors, false) && $this->retry < 3) {
|
||||
$errno = null;
|
||||
$error = null;
|
||||
++$this->retry;
|
||||
$this->makeRequest($path, $method, $parameters);
|
||||
}
|
||||
|
||||
if ($errno) {
|
||||
throw new CurlException($error, $errno);
|
||||
}
|
||||
|
||||
return new ApiResponse($statusCode, $responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry connect
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRetry()
|
||||
{
|
||||
return $this->retry;
|
||||
}
|
||||
}
|
|
@ -1,158 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace RetailCrm\Response;
|
||||
|
||||
use RetailCrm\Exception\InvalidJsonException;
|
||||
|
||||
/**
|
||||
* PHP version 5.3
|
||||
*
|
||||
* Response from retailCRM API
|
||||
*
|
||||
* @category RetailCrm
|
||||
* @package RetailCrm
|
||||
* @author RetailCrm <integration@retailcrm.ru>
|
||||
* @license https://opensource.org/licenses/MIT MIT License
|
||||
* @link http://www.retailcrm.ru/docs/Developers/ApiVersion3
|
||||
*/
|
||||
class ApiResponse implements \ArrayAccess
|
||||
{
|
||||
// HTTP response status code
|
||||
protected $statusCode;
|
||||
|
||||
// response assoc array
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* ApiResponse constructor.
|
||||
*
|
||||
* @param int $statusCode HTTP status code
|
||||
* @param mixed $responseBody HTTP body
|
||||
*
|
||||
* @throws InvalidJsonException
|
||||
*/
|
||||
public function __construct($statusCode, $responseBody = null)
|
||||
{
|
||||
$this->statusCode = (int) $statusCode;
|
||||
|
||||
if (!empty($responseBody)) {
|
||||
$response = json_decode($responseBody, true);
|
||||
|
||||
if (!$response && JSON_ERROR_NONE !== ($error = json_last_error())) {
|
||||
throw new InvalidJsonException(
|
||||
"Invalid JSON in the API response body. Error code #$error",
|
||||
$error
|
||||
);
|
||||
}
|
||||
|
||||
$this->response = $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return HTTP response status code
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode()
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP request was successful
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSuccessful()
|
||||
{
|
||||
return $this->statusCode < 400;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to access for the property throw class method
|
||||
*
|
||||
* @param string $name
|
||||
* @param $arguments
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call($name, $arguments)
|
||||
{
|
||||
// convert getSomeProperty to someProperty
|
||||
$propertyName = strtolower(substr($name, 3, 1)) . substr($name, 4);
|
||||
|
||||
if (!isset($this->response[$propertyName])) {
|
||||
throw new \InvalidArgumentException("Method \"$name\" not found");
|
||||
}
|
||||
|
||||
return $this->response[$propertyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow to access for the property throw object property
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($name)
|
||||
{
|
||||
if (!isset($this->response[$name])) {
|
||||
throw new \InvalidArgumentException("Property \"$name\" not found");
|
||||
}
|
||||
|
||||
return $this->response[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
throw new \BadMethodCallException('This activity not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
throw new \BadMethodCallException('This call not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->response[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (!isset($this->response[$offset])) {
|
||||
throw new \InvalidArgumentException("Property \"$offset\" not found");
|
||||
}
|
||||
|
||||
return $this->response[$offset];
|
||||
}
|
||||
}
|
0
models/.gitkeep
Normal file
0
models/.gitkeep
Normal file
17
phpcs.xml.dist
Normal file
17
phpcs.xml.dist
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
|
||||
<arg name="basepath" value="."/>
|
||||
<arg name="cache" value=".php_cs.cache"/>
|
||||
<arg name="colors"/>
|
||||
<arg name="extensions" value="php"/>
|
||||
|
||||
<rule ref="PSR12"/>
|
||||
|
||||
<file>src/</file>
|
||||
<file>tests/</file>
|
||||
|
||||
<exclude-pattern>src/Component/Serializer/Generator/*</exclude-pattern>
|
||||
<exclude-pattern>src/Component/Serializer/Parser/*</exclude-pattern>
|
||||
<exclude-pattern>src/Component/Serializer/ArraySupportDecorator.php</exclude-pattern>
|
||||
</ruleset>
|
21
phpdoc.dist.xml
Normal file
21
phpdoc.dist.xml
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<phpdocumentor
|
||||
configVersion="3"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://www.phpdoc.org"
|
||||
xsi:noNamespaceSchemaLocation="https://docs.phpdoc.org/latest/phpdoc.xsd"
|
||||
>
|
||||
<title>RetailCRM API Client</title>
|
||||
<paths>
|
||||
<output>docs/build/html</output>
|
||||
<cache>docs/build/cache</cache>
|
||||
</paths>
|
||||
<version number="latest">
|
||||
<api>
|
||||
<visibility>public</visibility>
|
||||
<source dsn=".">
|
||||
<path>src</path>
|
||||
</source>
|
||||
</api>
|
||||
</version>
|
||||
</phpdocumentor>
|
49
phpmd.xml
Normal file
49
phpmd.xml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="Ruleset"
|
||||
xmlns="http://pmd.sf.net/ruleset/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
|
||||
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
|
||||
<description>Ruleset</description>
|
||||
<rule ref="rulesets/controversial.xml" />
|
||||
<rule ref="rulesets/unusedcode.xml" />
|
||||
|
||||
<rule ref="rulesets/design.xml">
|
||||
<exclude name="CouplingBetweenObjects" />
|
||||
</rule>
|
||||
<rule ref="rulesets/cleancode.xml">
|
||||
<exclude name="StaticAccess" />
|
||||
</rule>
|
||||
<rule ref="rulesets/codesize.xml">
|
||||
<exclude name="TooManyPublicMethods" />
|
||||
<exclude name="TooManyFields" />
|
||||
</rule>
|
||||
<rule ref="rulesets/naming.xml">
|
||||
<exclude name="ShortVariable" />
|
||||
</rule>
|
||||
|
||||
<rule ref="rulesets/naming.xml/ShortVariable">
|
||||
<properties>
|
||||
<property name="minimum" value="2" />
|
||||
</properties>
|
||||
</rule>
|
||||
<rule ref="rulesets/codesize.xml/TooManyPublicMethods">
|
||||
<properties>
|
||||
<property name="maxmethods" value="20" />
|
||||
</properties>
|
||||
</rule>
|
||||
<rule ref="rulesets/codesize.xml/TooManyFields">
|
||||
<properties>
|
||||
<property name="maxfields" value="30" />
|
||||
</properties>
|
||||
</rule>
|
||||
<rule ref="rulesets/design.xml/CouplingBetweenObjects">
|
||||
<properties>
|
||||
<property name="maximum" value="15" />
|
||||
</properties>
|
||||
</rule>
|
||||
|
||||
<exclude-pattern>tests/*</exclude-pattern>
|
||||
<exclude-pattern>src/Component/Serializer/Generator/*</exclude-pattern>
|
||||
<exclude-pattern>src/Component/Serializer/Parser/*</exclude-pattern>
|
||||
</ruleset>
|
246
phpstan-baseline-serializer.neon
Normal file
246
phpstan-baseline-serializer.neon
Normal file
|
@ -0,0 +1,246 @@
|
|||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$config of static method Liip\\\\Serializer\\\\Configuration\\\\GeneratorConfiguration\\:\\:createFomArray\\(\\) expects array\\{default_group_combinations\\?\\: array\\<int, array\\<int, string\\>\\>\\|null, default_versions\\?\\: array\\<int, string\\>\\|null, classes\\?\\: array\\<class\\-string, array\\<string, mixed\\>\\>\\|null, options\\?\\: array\\<string, mixed\\>\\}, array\\{default_group_combinations\\: array\\{\\}, default_versions\\: array\\{\\}, classes\\: non\\-empty\\-array\\<string, array\\{\\}\\>\\} given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ModelsGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$classesToGenerate of class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator constructor expects array\\<int, class\\-string\\>, array\\<string\\> given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ModelsGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$method of method Liip\\\\Serializer\\\\Template\\\\Deserialization\\:\\:renderSetter\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Generator/DeserializerGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#4 \\$stack of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\DeserializerGenerator\\:\\:generateCodeForClass\\(\\) expects array\\<string, int\\<1, max\\>\\>, array given\\.$#"
|
||||
count: 2
|
||||
path: src/Component/Serializer/Generator/DeserializerGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\SerializerGenerator\\:\\:buildSerializerFunctionName\\(\\) should return string but returns string\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Generator/SerializerGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$method of method Liip\\\\Serializer\\\\Template\\\\Serialization\\:\\:renderGetter\\(\\) expects string, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Generator/SerializerGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$serializerGroups of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Generator\\\\SerializerGenerator\\:\\:generateCodeForClass\\(\\) expects array\\<int, string\\>, array given\\.$#"
|
||||
count: 4
|
||||
path: src/Component/Serializer/Generator/SerializerGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer extends generic class Doctrine\\\\Common\\\\Lexer\\\\AbstractLexer but does not specify its types\\: T, V$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer\\:\\:getType\\(\\) has parameter \\$value with no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Lexer\\:\\:parse\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$haystack of function stripos expects string, float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$haystack of function strpos expects string, float\\|int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Lexer.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) should return string but returns string\\|false\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:parse\\(\\) should return array but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitArrayType\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitCompoundType\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:visitSimpleType\\(\\) never returns string so it can be removed from the return type\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\ParserInterface\\:\\:parse\\(\\) return type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/ParserInterface.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:gatherClassAnnotations\\(\\) has parameter \\$reflectionClass with generic class ReflectionClass but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getMethodName\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getProperty\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getReturnType\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:getSerializedName\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:isPostDeserializeMethod\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:isVirtualProperty\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseClass\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseMethods\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parseProperties\\(\\) has parameter \\$reflClass with generic class ReflectionClass but does not specify its types\\: T$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSParser\\:\\:parsePropertyAnnotations\\(\\) has parameter \\$annotations with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Class Doctrine\\\\Common\\\\Collections\\\\ArrayCollection not found\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
||||
|
||||
-
|
||||
message: "#^Class Doctrine\\\\Common\\\\Collections\\\\Collection not found\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSTypeParser\\:\\:parseType\\(\\) has parameter \\$typeInfo with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#4 \\$traversableClass of class Liip\\\\MetadataParser\\\\Metadata\\\\PropertyTypeIterable constructor expects class\\-string\\<Traversable\\>\\|null, string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access property \\$value on RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\|null\\.$#"
|
||||
count: 2
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$string of function strlen expects string, int\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$value of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) expects int, int\\<min, 0\\>\\|int\\<4, 8\\>\\|int\\<11, max\\>\\|string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$value of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:getConstant\\(\\) expects int, int\\|string\\|null given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Parser\\:\\:\\$token with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Parser.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property object\\:\\:\\$position\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property object\\:\\:\\$type\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property object\\:\\:\\$value\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromArray\\(\\) has parameter \\$source with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromArray\\(\\) return type with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\:\\:fromObject\\(\\) return type with generic class RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token does not specify its types\\: T, V$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSCore\\\\Type\\\\Token\\<T of int\\|string,V of int\\|string\\>\\:\\:\\$type \\(\\(T of int\\|string\\)\\|null\\) does not accept int\\|string\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSCore/Type/Token.php
|
||||
|
||||
-
|
||||
message: "#^Cannot call method getParameters\\(\\) on ReflectionMethod\\|null\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\Parser\\\\JMSTypeParser\\:\\:\\$useArrayDateFormat has no type specified\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSTypeParser.php
|
329
phpstan-baseline.neon
Normal file
329
phpstan-baseline.neon
Normal file
|
@ -0,0 +1,329 @@
|
|||
parameters:
|
||||
excludePaths:
|
||||
- src/Component/Serializer/ArraySupportDecorator.php
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Builder\\\\ClientBuilder\\:\\:buildHandlersChain\\(\\) through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Builder/ClientBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Builder\\\\FilesystemCacheBuilder\\:\\:getCacheDirPath\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Builder/FilesystemCacheBuilder.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Client\\:\\:getBaseUrl\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Client.php
|
||||
|
||||
-
|
||||
message: "#^Cannot assign new offset to array\\<string\\>\\|string\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$array of function array_filter expects array, string given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$haystack of function in_array expects array, array\\<string\\>\\|string given\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Command\\\\CompilerPromptCommand\\:\\:COMPILER_PLUGIN through static\\:\\:\\.$#"
|
||||
count: 3
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Command\\\\CompilerPromptCommand\\:\\:activateAutoCompiler\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Command\\\\CompilerPromptCommand\\:\\:activatePlugin\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Command\\\\CompilerPromptCommand\\:\\:deactivateAutoCompiler\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Command/CompilerPromptCommand.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\ComposerLocator\\:\\:getBaseDirectory\\(\\) through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Component/ComposerLocator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\ComposerLocator\\:\\:getPackageComposerJson\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ComposerLocator.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\FilesIteratorChecksumGenerator\\:\\:\\$fileNameAccessor \\(callable\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FilesIteratorChecksumGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\FilesIteratorChecksumGenerator\\:\\:\\$hashFunc \\(callable\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FilesIteratorChecksumGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\FilesIteratorChecksumGenerator\\:\\:\\$keyTransformer \\(callable\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FilesIteratorChecksumGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\FormData\\\\FormEncoder\\:\\:processPostSerialize\\(\\) should return array but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/FormEncoder.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object of function get_class expects object, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/FormEncoder.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object of method ReflectionMethod\\:\\:invokeArgs\\(\\) expects object\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/FormEncoder.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<object\\>\\|object, class\\-string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/FormEncoder.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object of function get_class expects object, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/EntityStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$object of method ReflectionProperty\\:\\:getValue\\(\\) expects object\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/EntityStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<object\\>\\|object, class\\-string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/EntityStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to float\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Cannot cast mixed to int\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/SimpleTypeStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private property RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\Encode\\\\TypedArrayStrategy\\:\\:\\$innerTypesMatcher through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\Encode\\\\TypedArrayStrategy\\:\\:getInnerTypes\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\StrategyFactory\\:\\:TYPED_MATCHER through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Component/FormData/Strategy/StrategyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private property RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\StrategyFactory\\:\\:\\$simpleTypes through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/StrategyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\StrategyFactory\\:\\:getArrayInnerTypes\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/StrategyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\StrategyFactory\\:\\:getDateTimeFormat\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/StrategyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\FormData\\\\Strategy\\\\StrategyFactory\\:\\:isDateTime\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/FormData/Strategy/StrategyFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Component\\\\ModelsGenerator\\:\\:IGNORED_NAMESPACES through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ModelsGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\ModelsGenerator\\:\\:createDir\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ModelsGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\ModelsGenerator\\:\\:isNamespaceIgnored\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/ModelsGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\PhpFilesIterator\\:\\:\\$parent \\(Iterator\\<int\\|string, array\\|string\\>\\) does not accept RegexIterator\\<mixed, mixed, Traversable\\<TKey, TValue\\>\\>\\.$#"
|
||||
count: 1
|
||||
path: src/Component/PhpFilesIterator.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Component\\\\PhpFilesIterator\\:\\:\\$parent type has no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
path: src/Component/PhpFilesIterator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private constant RetailCrm\\\\Api\\\\Component\\\\PhpFilesIterator\\:\\:NAMESPACE_MATCHER through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/PhpFilesIterator.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ArraySupportDecorator\\:\\:fromArray\\(\\) should return array\\<int\\|string, mixed\\>\\|object but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ArraySupportDecorator\\:\\:getArrayValueType\\(\\) should return string but returns array\\<string\\>\\.$#"
|
||||
count: 2
|
||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$data of method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ArraySupportDecorator\\:\\:decodeArray\\(\\) expects array, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ArraySupportDecorator\\:\\:getArrayValueType\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ArraySupportDecorator\\:\\:isArrayType\\(\\) through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Component/Serializer/ArraySupportDecorator.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ModelsChecksumGenerator\\:\\:getStoredChecksums\\(\\) should return array\\<string, string\\> but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/ModelsChecksumGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Serializer\\\\ModelsChecksumGenerator\\:\\:getChecksumFileName\\(\\) through static\\:\\:\\.$#"
|
||||
count: 4
|
||||
path: src/Component/Serializer/ModelsChecksumGenerator.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$string2 of function strncmp expects string, class\\-string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#3 \\$annotation of static method Liip\\\\MetadataParser\\\\Exception\\\\ParseException\\:\\:unsupportedPropertyAnnotation\\(\\) expects string, class\\-string\\|false given\\.$#"
|
||||
count: 1
|
||||
path: src/Component/Serializer/Parser/JMSParser.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Component\\\\Transformer\\\\DateTimeTransformer\\:\\:createFromFormat\\(\\) through static\\:\\:\\.$#"
|
||||
count: 3
|
||||
path: src/Component/Transformer/DateTimeTransformer.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Event\\\\AbstractRequestEvent\\:\\:getApiKey\\(\\) should return string but returns array\\|string\\.$#"
|
||||
count: 1
|
||||
path: src/Event/AbstractRequestEvent.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Event\\\\AbstractRequestEvent\\:\\:\\$apiKey \\(string\\) does not accept array\\|string\\.$#"
|
||||
count: 1
|
||||
path: src/Event/AbstractRequestEvent.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Exception\\\\ApiException\\:\\:getErrorMessage\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Exception/ApiException.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Model\\\\Response\\\\ErrorResponse\\:\\:\\$errorMsg \\(string\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 2
|
||||
path: src/Factory/ApiExceptionFactory.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Model\\\\Response\\\\ErrorResponse\\:\\:\\$errors \\(array\\<string\\>\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Factory/ApiExceptionFactory.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe access to private property RetailCrm\\\\Api\\\\Handler\\\\Request\\\\PsrRequestHandler\\:\\:\\$methodsWithBody through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Request/PsrRequestHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Request\\\\RequestDataHandler\\:\\:queryShouldBeUsed\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Request/RequestDataHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Request\\\\RequestDataHandler\\:\\:throwEncodeException\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Request/RequestDataHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\AbstractResponseHandler\\:\\:unmarshalBody\\(\\) should return RetailCrm\\\\Api\\\\Interfaces\\\\ResponseInterface but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/AbstractResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\AbstractResponseHandler\\:\\:unmarshalBody\\(\\) should return RetailCrm\\\\Api\\\\Interfaces\\\\ResponseInterface but returns object\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/AbstractResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\AbstractResponseHandler\\:\\:unmarshalBodyArray\\(\\) should return array\\<int\\|string, mixed\\> but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/AbstractResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\AbstractResponseHandler\\:\\:throwUnmarshalError\\(\\) through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Handler/Response/AbstractResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\AccountNotFoundHandler\\:\\:isContentType\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/AccountNotFoundHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\FilesDownloadResponseHandler\\:\\:fileNameFromDisposition\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/FilesDownloadResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Handler\\\\Response\\\\FilesDownloadResponseHandler\\:\\:isFileRequest\\(\\) through static\\:\\:\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/FilesDownloadResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Property RetailCrm\\\\Api\\\\Model\\\\ResponseData\\:\\:\\$responseArray \\(array\\<int\\|string, mixed\\>\\) on left side of \\?\\? is not nullable\\.$#"
|
||||
count: 1
|
||||
path: src/Handler/Response/UnmarshalResponseHandler.php
|
||||
|
||||
-
|
||||
message: "#^Unsafe call to private method RetailCrm\\\\Api\\\\Model\\\\Entity\\\\Delivery\\\\TimeInterval\\:\\:createTime\\(\\) through static\\:\\:\\.$#"
|
||||
count: 2
|
||||
path: src/Model/Entity/Delivery/TimeInterval.php
|
||||
|
||||
-
|
||||
message: "#^Method RetailCrm\\\\Api\\\\ResourceGroup\\\\AbstractApiResourceGroup\\:\\:sendRequest\\(\\) should return RetailCrm\\\\Api\\\\Interfaces\\\\ResponseInterface but returns object\\.$#"
|
||||
count: 1
|
||||
path: src/ResourceGroup/AbstractApiResourceGroup.php
|
||||
|
9
phpstan.neon
Normal file
9
phpstan.neon
Normal file
|
@ -0,0 +1,9 @@
|
|||
includes:
|
||||
- phpstan-baseline-serializer.neon
|
||||
- phpstan-baseline.neon # TODO: This should be removed eventually.
|
||||
|
||||
parameters:
|
||||
level: max
|
||||
paths:
|
||||
- src
|
||||
- tests
|
|
@ -1,32 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="phpunit.xsd"
|
||||
bootstrap="./tests/bootstrap.php"
|
||||
colors="true"
|
||||
verbose="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="true">
|
||||
|
||||
<!-- Dummy values used to provide credentials. No need to change these. -->
|
||||
<php>
|
||||
<server name="CRM_URL" value="foo" />
|
||||
<server name="CRM_API_KEY" value="bar" />
|
||||
<server name="CRM_SITE" value="zoo" />
|
||||
<server name="CRM_STORE" value="moo" />
|
||||
<server name="CRM_PACK_ITEM" value="boo" />
|
||||
<server name="CRM_PACK_QUANTITY" value="goo" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="RetailCrm">
|
||||
<directory>tests/RetailCrm/Tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory suffix=".php">./src/RetailCrm</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="false"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
backupStaticAttributes="false"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="false"
|
||||
convertWarningsToExceptions="false"
|
||||
processIsolation="true"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnSkipped="false"
|
||||
stopOnRisky="false"
|
||||
>
|
||||
<coverage>
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
<directory>dev</directory>
|
||||
</include>
|
||||
<report>
|
||||
<clover outputFile="coverage.xml"/>
|
||||
</report>
|
||||
</coverage>
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<file>tests/src/Command/ClearModelsCommandTest.php</file>
|
||||
<file>tests/src/Command/GenerateModelsCommandTest.php</file>
|
||||
<file>tests/src/Command/VerifyModelsCommandTest.php</file>
|
||||
<directory>tests/src</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging>
|
||||
<junit outputFile="test-report.xml"/>
|
||||
</logging>
|
||||
<php>
|
||||
<ini name="memory_limit" value="4G" />
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
251
phpunit.xsd
251
phpunit.xsd
|
@ -1,251 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<xs:annotation>
|
||||
<xs:documentation source="http://www.phpunit.de/manual/3.7/en/appendixes.configuration.html">
|
||||
This Schema file defines the rules by which the XML configuration file of PHPUnit 3.7 may be structured.
|
||||
</xs:documentation>
|
||||
<xs:appinfo source="http://www.phpunit.de/manual/current/en/appendixes.configuration.html"/>
|
||||
</xs:annotation>
|
||||
<xs:element name="phpunit" type="phpUnitType">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Root Element</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:complexType name="filtersType">
|
||||
<xs:choice>
|
||||
<xs:sequence>
|
||||
<xs:element name="blacklist" type="filterType"/>
|
||||
<xs:element name="whitelist" type="whiteListType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:sequence>
|
||||
<xs:element name="whitelist" type="whiteListType"/>
|
||||
</xs:sequence>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="filterType">
|
||||
<xs:sequence>
|
||||
<xs:group ref="pathGroup"/>
|
||||
<xs:element name="exclude" maxOccurs="unbounded" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:group ref="pathGroup"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="whiteListType">
|
||||
<xs:complexContent>
|
||||
<xs:extension base="filterType">
|
||||
<xs:attribute name="processUncoveredFilesFromWhitelist" default="true" type="xs:boolean"/>
|
||||
</xs:extension>
|
||||
</xs:complexContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="groupsType">
|
||||
<xs:choice>
|
||||
<xs:sequence>
|
||||
<xs:element name="include" type="groupType"/>
|
||||
<xs:element name="exclude" type="groupType" minOccurs="0"/>
|
||||
</xs:sequence>
|
||||
<xs:sequence>
|
||||
<xs:element name="exclude" type="groupType"/>
|
||||
</xs:sequence>
|
||||
</xs:choice>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="groupType">
|
||||
<xs:sequence>
|
||||
<xs:element name="group" type="xs:string" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="listenersType">
|
||||
<xs:sequence>
|
||||
<xs:element name="listener" type="objectType" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="objectType">
|
||||
<xs:sequence>
|
||||
<xs:element name="arguments" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:group ref="argumentsGroup"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="class" type="xs:string" use="required"/>
|
||||
<xs:attribute name="file" type="xs:anyURI"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="arrayType">
|
||||
<xs:sequence>
|
||||
<xs:element name="element" type="argumentType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="argumentType">
|
||||
<xs:group ref="argumentChoice"/>
|
||||
<xs:attribute name="key" use="required"/>
|
||||
</xs:complexType>
|
||||
<xs:group name="argumentsGroup">
|
||||
<xs:sequence>
|
||||
<xs:element name="array" type="arrayType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="integer" type="xs:integer" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="string" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="double" type="xs:double" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="null" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="object" type="objectType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="file" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="directory" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
<xs:group name="argumentChoice">
|
||||
<xs:choice>
|
||||
<xs:element name="array" type="arrayType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="integer" type="xs:integer" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="string" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="double" type="xs:double" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="null" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="object" type="objectType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="file" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="directory" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:choice>
|
||||
</xs:group>
|
||||
<xs:complexType name="loggersType">
|
||||
<xs:sequence>
|
||||
<xs:element name="log" type="loggerType" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="loggerType">
|
||||
<xs:attribute name="type">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="coverage-html"/>
|
||||
<xs:enumeration value="coverage-clover"/>
|
||||
<xs:enumeration value="json"/>
|
||||
<xs:enumeration value="plain"/>
|
||||
<xs:enumeration value="tap"/>
|
||||
<xs:enumeration value="junit"/>
|
||||
<xs:enumeration value="testdox-html"/>
|
||||
<xs:enumeration value="testdox-text"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="target" type="xs:anyURI"/>
|
||||
<xs:attribute name="title" type="xs:string"/>
|
||||
<xs:attribute name="charset" type="xs:string" default="UTF-8"/>
|
||||
<xs:attribute name="yui" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="highlight" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="lowUpperBound" type="xs:nonNegativeInteger" default="35"/>
|
||||
<xs:attribute name="highLowerBound" type="xs:nonNegativeInteger" default="70"/>
|
||||
<xs:attribute name="logIncompleteSkipped" type="xs:boolean" default="false"/>
|
||||
</xs:complexType>
|
||||
<xs:group name="pathGroup">
|
||||
<xs:sequence>
|
||||
<xs:element name="directory" type="directoryFilterType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="file" type="fileFilterType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:group>
|
||||
<xs:complexType name="directoryFilterType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:anyURI">
|
||||
<xs:attribute type="xs:string" name="suffix" default="Test.php"/>
|
||||
<xs:attributeGroup ref="phpVersionGroup"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="fileFilterType">
|
||||
<xs:simpleContent>
|
||||
<xs:extension base="xs:anyURI">
|
||||
<xs:attributeGroup ref="phpVersionGroup"/>
|
||||
</xs:extension>
|
||||
</xs:simpleContent>
|
||||
</xs:complexType>
|
||||
<xs:attributeGroup name="phpVersionGroup">
|
||||
<xs:attribute name="phpVersion" type="xs:string" default="5.3.0"/>
|
||||
<xs:attribute name="phpVersionOperator" type="xs:string" default=">="/>
|
||||
</xs:attributeGroup>
|
||||
<xs:complexType name="phpType">
|
||||
<xs:sequence>
|
||||
<xs:element name="includePath" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="ini" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="const" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="var" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="env" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="post" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="get" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="cookie" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="server" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="files" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
<xs:element name="request" type="namedValueType" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="namedValueType">
|
||||
<xs:attribute name="name" use="required" type="xs:string"/>
|
||||
<xs:attribute name="value" use="required" type="xs:anySimpleType"/>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="phpUnitType">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The main type specifying the document structure</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:group ref="configGroup"/>
|
||||
<xs:attributeGroup ref="configAttributeGroup"/>
|
||||
</xs:complexType>
|
||||
<xs:attributeGroup name="configAttributeGroup">
|
||||
<xs:attribute name="backupGlobals" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="backupStaticAttributes" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="bootstrap" type="xs:anyURI"/>
|
||||
<xs:attribute name="cacheTokens" type="xs:boolean"/>
|
||||
<xs:attribute name="colors" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="convertErrorsToExceptions" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="convertNoticesToExceptions" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="convertWarningsToExceptions" type="xs:boolean" default="true"/>
|
||||
<xs:attribute name="forceCoversAnnotation" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="mapTestClassNameToCoveredClassName" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="printerClass" type="xs:string" default="PHPUnit_TextUI_ResultPrinter"/>
|
||||
<xs:attribute name="printerFile" type="xs:anyURI"/>
|
||||
<xs:attribute name="processIsolation" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="stopOnError" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="stopOnFailure" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="stopOnIncomplete" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="stopOnSkipped" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="strict" type="xs:boolean" default="false"/>
|
||||
<xs:attribute name="testSuiteLoaderClass" type="xs:string" default="PHPUnit_Runner_StandardTestSuiteLoader"/>
|
||||
<xs:attribute name="testSuiteLoaderFile" type="xs:anyURI"/>
|
||||
<xs:attribute name="timeoutForSmallTests" type="xs:integer" default="1"/>
|
||||
<xs:attribute name="timeoutForMediumTests" type="xs:integer" default="10"/>
|
||||
<xs:attribute name="timeoutForLargeTests" type="xs:integer" default="60"/>
|
||||
<xs:attribute name="verbose" type="xs:boolean" default="false"/>
|
||||
</xs:attributeGroup>
|
||||
<xs:group name="configGroup">
|
||||
<xs:all>
|
||||
<xs:element ref="testSuiteFacet" minOccurs="0"/>
|
||||
<xs:element name="groups" type="groupsType" minOccurs="0"/>
|
||||
<xs:element name="filter" type="filtersType" minOccurs="0"/>
|
||||
<xs:element name="logging" type="loggersType" minOccurs="0"/>
|
||||
<xs:element name="listeners" type="listenersType" minOccurs="0"/>
|
||||
<xs:element name="php" type="phpType" minOccurs="0"/>
|
||||
<xs:element name="selenium" type="seleniumType" minOccurs="0"/>
|
||||
</xs:all>
|
||||
</xs:group>
|
||||
<xs:complexType name="seleniumType">
|
||||
<xs:sequence>
|
||||
<xs:element name="browser" type="browserType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="browserType">
|
||||
<xs:attribute name="name" type="xs:string"/>
|
||||
<xs:attribute name="browser" type="xs:string"/>
|
||||
<xs:attribute name="host" type="xs:anyURI"/>
|
||||
<xs:attribute name="port" type="xs:nonNegativeInteger"/>
|
||||
<xs:attribute name="timeout" type="xs:nonNegativeInteger"/>
|
||||
</xs:complexType>
|
||||
<xs:element name="testSuiteFacet" abstract="true"/>
|
||||
<xs:element name="testsuite" type="testSuiteType" substitutionGroup="testSuiteFacet"/>
|
||||
<xs:element name="testsuites" type="testSuitesType" substitutionGroup="testSuiteFacet"/>
|
||||
<xs:complexType name="testSuitesType">
|
||||
<xs:sequence>
|
||||
<xs:element name="testsuite" type="testSuiteType"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
<xs:complexType name="testSuiteType">
|
||||
<xs:sequence>
|
||||
<xs:group ref="pathGroup"/>
|
||||
<xs:element name="exclude" type="xs:anyURI" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
<xs:attribute name="name" type="xs:string" use="required"/>
|
||||
</xs:complexType>
|
||||
</xs:schema>
|
492
src/Builder/ClientBuilder.php
Normal file
492
src/Builder/ClientBuilder.php
Normal file
|
@ -0,0 +1,492 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ClientBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Builder;
|
||||
|
||||
use Http\Discovery\Psr17FactoryDiscovery;
|
||||
use Http\Discovery\Psr18ClientDiscovery;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Http\Message\UriFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RetailCrm\Api\Client;
|
||||
use RetailCrm\Api\Component\Transformer\RequestTransformer;
|
||||
use RetailCrm\Api\Component\Transformer\ResponseTransformer;
|
||||
use RetailCrm\Api\Exception\Client\BuilderException;
|
||||
use RetailCrm\Api\Factory\ApiExceptionFactory;
|
||||
use RetailCrm\Api\Factory\RequestPipelineFactory;
|
||||
use RetailCrm\Api\Factory\ResponsePipelineFactory;
|
||||
use RetailCrm\Api\Interfaces\ApiExceptionFactoryAwareInterface;
|
||||
use RetailCrm\Api\Interfaces\BuilderInterface;
|
||||
use RetailCrm\Api\Interfaces\EventDispatcherAwareInterface;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
use RetailCrm\Api\Interfaces\HandlerInterface;
|
||||
use RetailCrm\Api\Interfaces\PsrFactoriesAwareInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestTransformerInterface;
|
||||
use RetailCrm\Api\Interfaces\ResponseTransformerInterface;
|
||||
use RetailCrm\Api\Interfaces\SerializerAwareInterface;
|
||||
use RetailCrm\Api\Traits\EventDispatcherAwareTrait;
|
||||
|
||||
/**
|
||||
* Class ClientBuilder
|
||||
*
|
||||
* @category ClientBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||||
*/
|
||||
class ClientBuilder implements BuilderInterface, EventDispatcherAwareInterface
|
||||
{
|
||||
use EventDispatcherAwareTrait;
|
||||
|
||||
/** @var string */
|
||||
private $apiUrl;
|
||||
|
||||
/** @var HandlerInterface|null */
|
||||
private $authenticator;
|
||||
|
||||
/** @var ClientInterface|null */
|
||||
private $httpClient;
|
||||
|
||||
/** @var \Psr\Log\LoggerInterface|null */
|
||||
private $debugLogger;
|
||||
|
||||
/** @var RequestTransformerInterface|null */
|
||||
private $requestTransformer;
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\ResponseTransformerInterface|null */
|
||||
protected $responseTransformer;
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\FormEncoderInterface|null */
|
||||
private $formEncoder;
|
||||
|
||||
/** @var \Psr\Http\Message\StreamFactoryInterface|null */
|
||||
private $streamFactory;
|
||||
|
||||
/** @var \Psr\Http\Message\RequestFactoryInterface|null */
|
||||
private $requestFactory;
|
||||
|
||||
/** @var \Psr\Http\Message\UriFactoryInterface|null */
|
||||
private $uriFactory;
|
||||
|
||||
/** @var \RetailCrm\Api\Factory\ApiExceptionFactory|null */
|
||||
private $apiExceptionFactory;
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\HandlerInterface[] */
|
||||
private $requestHandlers = [];
|
||||
|
||||
/** @var \RetailCrm\Api\Interfaces\HandlerInterface[] */
|
||||
private $responseHandlers = [];
|
||||
|
||||
/**
|
||||
* API URL. Looks like this: "https://test.retailcrm.pro/"
|
||||
*
|
||||
* @param string $apiUrl
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setApiUrl(string $apiUrl): ClientBuilder
|
||||
{
|
||||
$this->apiUrl = $apiUrl;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request authenticator to append into request transformer pipeline.
|
||||
*
|
||||
* Do not use it if you already added a proper authenticator in the pipeline manually.
|
||||
* You can use this method to drop authenticator from client builder (use null).
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface|null $authenticator
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setAuthenticatorHandler(?HandlerInterface $authenticator): ClientBuilder
|
||||
{
|
||||
$this->authenticator = $authenticator;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set your PSR-18 HTTP client.
|
||||
*
|
||||
* Service discovery will be used if no client has been provided.
|
||||
*
|
||||
* @param \Psr\Http\Client\ClientInterface|null $httpClient
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setHttpClient(?ClientInterface $httpClient): ClientBuilder
|
||||
{
|
||||
$this->httpClient = $httpClient;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set debug logger.
|
||||
*
|
||||
* The provided logger will be used to record all requests and responses.
|
||||
* This feature consumes a lot of resources and shouldn't be used in production.
|
||||
*
|
||||
* @param \Psr\Log\LoggerInterface|null $debugLogger
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setDebugLogger(?LoggerInterface $debugLogger): ClientBuilder
|
||||
{
|
||||
$this->debugLogger = $debugLogger;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set request transformer into API client.
|
||||
*
|
||||
* You can use this method to set your request transformer which will execute the pipeline.
|
||||
* The default request transformer doesn't do anything besides calling provided chain of handlers.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestTransformerInterface|null $requestTransformer
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setRequestTransformer(?RequestTransformerInterface $requestTransformer): ClientBuilder
|
||||
{
|
||||
$this->requestTransformer = $requestTransformer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set response transformer into API client.
|
||||
*
|
||||
* You can use this method to set your response transformer which will execute the pipeline.
|
||||
* The default response transformer doesn't do anything besides calling provided chain of handlers.
|
||||
* The serializer instance for the request pipeline can be inferred from the provided FormEncoder instance.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\ResponseTransformerInterface|null $responseTransformer
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setResponseTransformer(?ResponseTransformerInterface $responseTransformer): ClientBuilder
|
||||
{
|
||||
$this->responseTransformer = $responseTransformer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set form encoder into API client.
|
||||
*
|
||||
* Form encoder is a vital part of the API client. Its purpose is to transform provided request models
|
||||
* into form-data. The result will be used as a query or POST body (depends on request type).
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\FormEncoderInterface $formEncoder
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setFormEncoder(FormEncoderInterface $formEncoder): ClientBuilder
|
||||
{
|
||||
$this->formEncoder = $formEncoder;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible stream factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\StreamFactoryInterface|null $streamFactory
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setStreamFactory(?StreamFactoryInterface $streamFactory): ClientBuilder
|
||||
{
|
||||
$this->streamFactory = $streamFactory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible request factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\RequestFactoryInterface|null $requestFactory
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setRequestFactory(?RequestFactoryInterface $requestFactory): ClientBuilder
|
||||
{
|
||||
$this->requestFactory = $requestFactory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets PSR-17 compatible URI factory. You can skip this step if you want to use service discovery.
|
||||
*
|
||||
* @param \Psr\Http\Message\UriFactoryInterface|null $uriFactory
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function setUriFactory(?UriFactoryInterface $uriFactory): ClientBuilder
|
||||
{
|
||||
$this->uriFactory = $uriFactory;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an additional request handler into the request processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface $handler
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function appendRequestHandler(HandlerInterface $handler): ClientBuilder
|
||||
{
|
||||
$this->requestHandlers[] = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends an additional response handler into the response processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface $handler
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function appendResponseHandler(HandlerInterface $handler): ClientBuilder
|
||||
{
|
||||
$this->responseHandlers[] = $handler;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends additional request handlers into the request processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function appendRequestHandlers(array $handlers): ClientBuilder
|
||||
{
|
||||
foreach ($handlers as $handler) {
|
||||
$this->appendRequestHandler($handler);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends additional response handlers into the response processing chain.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\HandlerInterface[] $handlers
|
||||
*
|
||||
* @return ClientBuilder
|
||||
*/
|
||||
public function appendResponseHandlers(array $handlers): ClientBuilder
|
||||
{
|
||||
foreach ($handlers as $handler) {
|
||||
$this->appendResponseHandler($handler);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds client with provided dependencies.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function build(): Client
|
||||
{
|
||||
$this->validateBuilder();
|
||||
|
||||
if (
|
||||
null !== $this->authenticator &&
|
||||
null !== $this->requestTransformer &&
|
||||
null !== $this->requestTransformer->getHandler()
|
||||
) {
|
||||
$this->requestTransformer->getHandler()->append($this->authenticator);
|
||||
}
|
||||
|
||||
if (null === $this->requestTransformer) {
|
||||
$this->requestTransformer = $this->buildRequestTransformer();
|
||||
}
|
||||
|
||||
if (null === $this->responseTransformer) {
|
||||
$this->responseTransformer = $this->buildResponseTransformer();
|
||||
}
|
||||
|
||||
$this->appendAdditionalRequestHandlers();
|
||||
$this->appendAdditionalResponseHandlers();
|
||||
|
||||
return new Client(
|
||||
$this->apiUrl,
|
||||
$this->httpClient ?: Psr18ClientDiscovery::find(),
|
||||
$this->requestTransformer, // @phpstan-ignore-line
|
||||
$this->responseTransformer, // @phpstan-ignore-line
|
||||
$this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(),
|
||||
$this->eventDispatcher,
|
||||
$this->debugLogger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if builder is ready to build a Client instance.
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function validateBuilder(): void
|
||||
{
|
||||
if (empty($this->apiUrl)) {
|
||||
throw new BuilderException('apiUrl must not be empty', ['apiUrl']);
|
||||
}
|
||||
|
||||
if (empty($this->authenticator) && empty($this->requestTransformer)) {
|
||||
throw new BuilderException(
|
||||
'Authenticator or RequestTransformer must be present',
|
||||
['authenticator', 'requestTransformer']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends additional request handlers into the request and response processor chain (if needed).
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function appendAdditionalRequestHandlers(): void
|
||||
{
|
||||
if (
|
||||
null !== $this->requestTransformer &&
|
||||
null !== $this->requestTransformer->getHandler() &&
|
||||
count($this->requestHandlers) > 0
|
||||
) {
|
||||
foreach ($this->requestHandlers as $handler) {
|
||||
if ($handler instanceof PsrFactoriesAwareInterface) {
|
||||
$handler->setRequestFactory($this->requestFactory ?: Psr17FactoryDiscovery::findRequestFactory());
|
||||
$handler->setStreamFactory($this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory());
|
||||
$handler->setUriFactory($this->uriFactory ?: Psr17FactoryDiscovery::findUriFactory());
|
||||
}
|
||||
}
|
||||
|
||||
$this->requestTransformer->getHandler()->append(static::buildHandlersChain($this->requestHandlers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends additional response handlers into the request and response processor chain (if needed).
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
private function appendAdditionalResponseHandlers(): void
|
||||
{
|
||||
if (
|
||||
null !== $this->responseTransformer &&
|
||||
null !== $this->responseTransformer->getHandler() &&
|
||||
count($this->responseHandlers) > 0
|
||||
) {
|
||||
foreach ($this->responseHandlers as $handler) {
|
||||
if ($handler instanceof SerializerAwareInterface && null !== $this->formEncoder) {
|
||||
$handler->setSerializer($this->formEncoder->getSerializer());
|
||||
}
|
||||
|
||||
if ($handler instanceof ApiExceptionFactoryAwareInterface && null !== $this->apiExceptionFactory) {
|
||||
$handler->setApiExceptionFactory($this->apiExceptionFactory);
|
||||
}
|
||||
|
||||
if ($handler instanceof EventDispatcherAwareInterface) {
|
||||
$handler->setEventDispatcher($this->eventDispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
$this->responseTransformer->getHandler()->append(static::buildHandlersChain($this->responseHandlers));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds RequestTransformer with default pipeline and authenticator.
|
||||
*
|
||||
* @return \RetailCrm\Api\Component\Transformer\RequestTransformer
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function buildRequestTransformer(): RequestTransformer
|
||||
{
|
||||
if (null === $this->formEncoder) {
|
||||
throw new BuilderException(
|
||||
"You must provide a FormEncoder instance in order to delegate " .
|
||||
"RequestTransformer instantiation to the ClientBuilder."
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $this->authenticator) {
|
||||
throw new BuilderException(
|
||||
"You must provide an authenticator handler instance in order to delegate " .
|
||||
"RequestTransformer instantiation to the ClientBuilder."
|
||||
);
|
||||
}
|
||||
|
||||
return new RequestTransformer(
|
||||
RequestPipelineFactory::createDefaultPipeline(
|
||||
$this->formEncoder,
|
||||
$this->uriFactory ?: Psr17FactoryDiscovery::findUriFactory(),
|
||||
$this->requestFactory ?: Psr17FactoryDiscovery::findRequestFactory(),
|
||||
$this->streamFactory ?: Psr17FactoryDiscovery::findStreamFactory(),
|
||||
$this->authenticator
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds ResponseTransformer.
|
||||
*
|
||||
* @return \RetailCrm\Api\Component\Transformer\ResponseTransformer
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function buildResponseTransformer(): ResponseTransformer
|
||||
{
|
||||
if (null === $this->formEncoder) {
|
||||
throw new BuilderException(
|
||||
"You must provide a FormEncoder instance in order to delegate " .
|
||||
"ResponseTransformer instantiation to the ClientBuilder."
|
||||
);
|
||||
}
|
||||
|
||||
if (null === $this->apiExceptionFactory) {
|
||||
$this->apiExceptionFactory = new ApiExceptionFactory();
|
||||
}
|
||||
|
||||
return new ResponseTransformer(ResponsePipelineFactory::createDefaultPipeline(
|
||||
$this->formEncoder->getSerializer(),
|
||||
$this->apiExceptionFactory,
|
||||
$this->eventDispatcher
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect all handlers in the array into chain, return first handler.
|
||||
*
|
||||
* @param HandlerInterface[] $handlers
|
||||
*
|
||||
* @return \RetailCrm\Api\Interfaces\HandlerInterface
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private static function buildHandlersChain(array $handlers): HandlerInterface
|
||||
{
|
||||
if (empty($handlers)) {
|
||||
throw new BuilderException('Supplied handlers chain must contain at least one handler');
|
||||
}
|
||||
|
||||
if (1 === count($handlers)) {
|
||||
return $handlers[0];
|
||||
}
|
||||
|
||||
for ($i = 0, $iMax = count($handlers) - 1; $i < $iMax; $i++) {
|
||||
$handlers[$i]->setNext($handlers[$i + 1]);
|
||||
}
|
||||
|
||||
return $handlers[0];
|
||||
}
|
||||
}
|
89
src/Builder/FilesystemCacheBuilder.php
Normal file
89
src/Builder/FilesystemCacheBuilder.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category FilesystemCacheBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Builder;
|
||||
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use RetailCrm\Api\Enum\CacheDirectories;
|
||||
use RetailCrm\Api\Exception\Client\BuilderException;
|
||||
use RetailCrm\Api\Interfaces\BuilderInterface;
|
||||
|
||||
/**
|
||||
* Class FilesystemCacheBuilder
|
||||
*
|
||||
* @category FilesystemCacheBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*/
|
||||
class FilesystemCacheBuilder implements BuilderInterface
|
||||
{
|
||||
/** @var string */
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* @param string $cacheDir
|
||||
*
|
||||
* @return FilesystemCacheBuilder
|
||||
*/
|
||||
public function setCacheDir(string $cacheDir): FilesystemCacheBuilder
|
||||
{
|
||||
$this->cacheDir = $cacheDir;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns filesystem cache.
|
||||
*
|
||||
* @return CacheItemPoolInterface
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function build(): CacheItemPoolInterface
|
||||
{
|
||||
if (empty($this->cacheDir)) {
|
||||
return new FilesystemAdapter(CacheDirectories::DIR_NAME);
|
||||
}
|
||||
|
||||
$cacheDir = static::getCacheDirPath($this->cacheDir);
|
||||
$this->createDir($cacheDir);
|
||||
|
||||
return new FilesystemAdapter('', 0, $cacheDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function createDir(string $dir): void
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) {
|
||||
throw new BuilderException(sprintf('Could not create directory "%s".', $dir));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns target cache dir for cache.
|
||||
*
|
||||
* @param string $cacheDir
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getCacheDirPath(string $cacheDir): string
|
||||
{
|
||||
if ('' !== $cacheDir && DIRECTORY_SEPARATOR !== $cacheDir[strlen($cacheDir) - 1]) {
|
||||
$cacheDir .= DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
return $cacheDir . CacheDirectories::MAIN_DIR . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
}
|
148
src/Builder/FormEncoderBuilder.php
Normal file
148
src/Builder/FormEncoderBuilder.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category FormEncoderBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Builder;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\PsrCachedReader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\Cache\CacheItemPoolInterface;
|
||||
use RetailCrm\Api\Component\FormData\FormEncoder;
|
||||
use RetailCrm\Api\Factory\SerializerFactory;
|
||||
use RetailCrm\Api\Interfaces\BuilderInterface;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
|
||||
/**
|
||||
* Class FormEncoderBuilder
|
||||
*
|
||||
* @category FormEncoderBuilder
|
||||
* @package RetailCrm\Api\Builder
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class FormEncoderBuilder implements BuilderInterface
|
||||
{
|
||||
/** @var CacheItemPoolInterface|null */
|
||||
private $cache;
|
||||
|
||||
/** @var \Doctrine\Common\Annotations\Reader */
|
||||
private $annotationReader;
|
||||
|
||||
/** @var \RetailCrm\Api\Builder\FilesystemCacheBuilder */
|
||||
private $fsCacheBuilder;
|
||||
|
||||
/** @var \Liip\Serializer\SerializerInterface */
|
||||
private $serializer;
|
||||
|
||||
/**
|
||||
* FormEncoderBuilder constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->fsCacheBuilder = new FilesystemCacheBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cache directory.
|
||||
*
|
||||
* This directory will be used by FormEncoder component and underlying serializer to store annotations cache.
|
||||
* Annotations parsing consumes a lot of resources, which is the reason why you should cache results.
|
||||
*
|
||||
* @param string $cacheDir
|
||||
*
|
||||
* @return FormEncoderBuilder
|
||||
*/
|
||||
public function setCacheDir(string $cacheDir): FormEncoderBuilder
|
||||
{
|
||||
$this->fsCacheBuilder->setCacheDir($cacheDir);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cache implementation.
|
||||
*
|
||||
* This cache implementation will be used by FormEncoder component and underlying serializer to store
|
||||
* annotations cache. Any cache from `symfony/cache` should work just fine.
|
||||
*
|
||||
* @param \Psr\Cache\CacheItemPoolInterface $cache
|
||||
*
|
||||
* @return FormEncoderBuilder
|
||||
*/
|
||||
public function setCache(CacheItemPoolInterface $cache): FormEncoderBuilder
|
||||
{
|
||||
$this->cache = $cache;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets serializer implementation.
|
||||
*
|
||||
* This serializer implementation will be used by FormEncoder component.
|
||||
*
|
||||
* @param \Liip\Serializer\SerializerInterface $serializer
|
||||
*
|
||||
* @return FormEncoderBuilder
|
||||
*/
|
||||
public function setSerializer(SerializerInterface $serializer): FormEncoderBuilder
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds FormEncoder.
|
||||
*
|
||||
* **Note:** Cache won't be set into provided serializer instance. It only works for instance built by
|
||||
* this builder. You must manually inject the proper cache into the custom JMS Serializer instance.
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function build(): FormEncoderInterface
|
||||
{
|
||||
$this->buildCache();
|
||||
$this->buildAnnotationReader();
|
||||
$this->buildSerializer();
|
||||
|
||||
return new FormEncoder($this->serializer, $this->annotationReader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds cache if needed.
|
||||
*
|
||||
* @throws \RetailCrm\Api\Exception\Client\BuilderException
|
||||
*/
|
||||
private function buildCache(): void
|
||||
{
|
||||
if (null === $this->cache) {
|
||||
$this->cache = $this->fsCacheBuilder->build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds annotation reader.
|
||||
*/
|
||||
private function buildAnnotationReader(): void
|
||||
{
|
||||
$this->annotationReader = new AnnotationReader();
|
||||
|
||||
if (null !== $this->cache) {
|
||||
$this->annotationReader = new PsrCachedReader(new AnnotationReader(), $this->cache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds serializer.
|
||||
*/
|
||||
private function buildSerializer(): void
|
||||
{
|
||||
if (null === $this->serializer) {
|
||||
$this->serializer = SerializerFactory::create();
|
||||
}
|
||||
}
|
||||
}
|
403
src/Client.php
Normal file
403
src/Client.php
Normal file
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Client
|
||||
* @package RetailCrm\Api
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api;
|
||||
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Client\ClientInterface;
|
||||
use Psr\Http\Message\StreamFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RetailCrm\Api\Component\Utils;
|
||||
use RetailCrm\Api\Interfaces\RequestTransformerInterface;
|
||||
use RetailCrm\Api\Interfaces\ResponseTransformerInterface;
|
||||
use RetailCrm\Api\ResourceGroup\Api;
|
||||
use RetailCrm\Api\ResourceGroup\Costs;
|
||||
use RetailCrm\Api\ResourceGroup\CustomerInteraction;
|
||||
use RetailCrm\Api\ResourceGroup\Customers;
|
||||
use RetailCrm\Api\ResourceGroup\CustomersCorporate;
|
||||
use RetailCrm\Api\ResourceGroup\CustomFields;
|
||||
use RetailCrm\Api\ResourceGroup\CustomMethods;
|
||||
use RetailCrm\Api\ResourceGroup\Delivery;
|
||||
use RetailCrm\Api\ResourceGroup\Features;
|
||||
use RetailCrm\Api\ResourceGroup\Files;
|
||||
use RetailCrm\Api\ResourceGroup\Integration;
|
||||
use RetailCrm\Api\ResourceGroup\Inventories;
|
||||
use RetailCrm\Api\ResourceGroup\Loyalty;
|
||||
use RetailCrm\Api\ResourceGroup\Notifications;
|
||||
use RetailCrm\Api\ResourceGroup\Orders;
|
||||
use RetailCrm\Api\ResourceGroup\Packs;
|
||||
use RetailCrm\Api\ResourceGroup\Payments;
|
||||
use RetailCrm\Api\ResourceGroup\References;
|
||||
use RetailCrm\Api\ResourceGroup\Segments;
|
||||
use RetailCrm\Api\ResourceGroup\Settings;
|
||||
use RetailCrm\Api\ResourceGroup\Statistics;
|
||||
use RetailCrm\Api\ResourceGroup\Store;
|
||||
use RetailCrm\Api\ResourceGroup\Tasks;
|
||||
use RetailCrm\Api\ResourceGroup\Telephony;
|
||||
use RetailCrm\Api\ResourceGroup\Users;
|
||||
use RetailCrm\Api\ResourceGroup\Verification;
|
||||
use RetailCrm\Api\ResourceGroup\WebAnalytics;
|
||||
|
||||
/**
|
||||
* Class Client
|
||||
*
|
||||
* Do not instantiate API client directly! Use `ClientFactory`, `SimpleClientFactory` or `ClientBuilder`.
|
||||
*
|
||||
* @category Client
|
||||
* @package RetailCrm\Api
|
||||
* @see \RetailCrm\Api\Factory\ClientFactory
|
||||
* @see \RetailCrm\Api\Factory\SimpleClientFactory
|
||||
* @see \RetailCrm\Api\Builder\ClientBuilder
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class Client
|
||||
{
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Api */
|
||||
public $api;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Costs */
|
||||
public $costs;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\CustomFields */
|
||||
public $customFields;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\CustomerInteraction */
|
||||
public $customerInteraction;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Customers */
|
||||
public $customers;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\CustomersCorporate */
|
||||
public $customersCorporate;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Delivery */
|
||||
public $delivery;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Features */
|
||||
public $features;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Files */
|
||||
public $files;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Integration */
|
||||
public $integration;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Loyalty */
|
||||
public $loyalty;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Notifications */
|
||||
public $notifications;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Orders */
|
||||
public $orders;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Packs */
|
||||
public $packs;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Payments */
|
||||
public $payments;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\References */
|
||||
public $references;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Segments */
|
||||
public $segments;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Settings */
|
||||
public $settings;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Store */
|
||||
public $store;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Tasks */
|
||||
public $tasks;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Telephony */
|
||||
public $telephony;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Users */
|
||||
public $users;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Verification */
|
||||
public $verification;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\Statistics */
|
||||
public $statistics;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\CustomMethods */
|
||||
public $customMethods;
|
||||
|
||||
/** @var \RetailCrm\Api\ResourceGroup\WebAnalytics */
|
||||
public $webAnalytics;
|
||||
|
||||
/** @var StreamFactoryInterface */
|
||||
private $streamFactory;
|
||||
|
||||
/**
|
||||
* Client constructor.
|
||||
*
|
||||
* @param string $apiUrl
|
||||
* @param \Psr\Http\Client\ClientInterface $httpClient
|
||||
* @param \RetailCrm\Api\Interfaces\RequestTransformerInterface $requestTransformer
|
||||
* @param \RetailCrm\Api\Interfaces\ResponseTransformerInterface $responseTransformer
|
||||
* @param \Psr\Http\Message\StreamFactoryInterface $streamFactory
|
||||
* @param \Psr\EventDispatcher\EventDispatcherInterface|null $eventDispatcher
|
||||
* @param \Psr\Log\LoggerInterface|null $logger
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @todo Maybe initialize children groups using different method?
|
||||
*/
|
||||
public function __construct(
|
||||
string $apiUrl,
|
||||
ClientInterface $httpClient,
|
||||
RequestTransformerInterface $requestTransformer,
|
||||
ResponseTransformerInterface $responseTransformer,
|
||||
StreamFactoryInterface $streamFactory,
|
||||
?EventDispatcherInterface $eventDispatcher = null,
|
||||
?LoggerInterface $logger = null
|
||||
) {
|
||||
$url = static::getBaseUrl($apiUrl);
|
||||
|
||||
$this->streamFactory = $streamFactory;
|
||||
|
||||
$this->api = new Api(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->costs = new Costs(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customFields = new CustomFields(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customerInteraction = new CustomerInteraction(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customers = new Customers(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customersCorporate = new CustomersCorporate(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->delivery = new Delivery(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->features = new Features(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->files = new Files(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->integration = new Integration(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->loyalty = new Loyalty(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->notifications = new Notifications(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->orders = new Orders(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->packs = new Packs(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->payments = new Payments(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->references = new References(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->segments = new Segments(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->settings = new Settings(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->store = new Store(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->tasks = new Tasks(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->telephony = new Telephony(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->users = new Users(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->verification = new Verification(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->statistics = new Statistics(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->customMethods = new CustomMethods(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
$this->webAnalytics = new WebAnalytics(
|
||||
$url,
|
||||
$httpClient,
|
||||
$requestTransformer,
|
||||
$responseTransformer,
|
||||
$eventDispatcher,
|
||||
$logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PSR-17 stream factory.
|
||||
*
|
||||
* StreamFactory can be used to create a PSR-7 StreamInterface from various sources.
|
||||
*
|
||||
* @return \Psr\Http\Message\StreamFactoryInterface
|
||||
*/
|
||||
public function getStreamFactory(): StreamFactoryInterface
|
||||
{
|
||||
return $this->streamFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses provided URL, builds API url with version out of it.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getBaseUrl(string $url): string
|
||||
{
|
||||
return Utils::removeTrailingSlash($url) . '/api/v5';
|
||||
}
|
||||
}
|
54
src/Command/AbstractModelsProcessorCommand.php
Normal file
54
src/Command/AbstractModelsProcessorCommand.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category AbstractModelsProcessorCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Command;
|
||||
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractModelsProcessorCommand
|
||||
*
|
||||
* @category AbstractModelsProcessorCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
* @internal
|
||||
*/
|
||||
abstract class AbstractModelsProcessorCommand extends Command
|
||||
{
|
||||
/**
|
||||
* Returns true if "-v" was provided.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isVerbose(OutputInterface $output): bool
|
||||
{
|
||||
return $output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory
|
||||
*
|
||||
* @param string $dir
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
protected static function createDir(string $dir): void
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) {
|
||||
throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
|
||||
}
|
||||
}
|
||||
}
|
86
src/Command/ClearModelsCommand.php
Normal file
86
src/Command/ClearModelsCommand.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ClearModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Command;
|
||||
|
||||
use RetailCrm\Api\Component\Utils;
|
||||
use RetailCrm\Api\Component\PhpFilesIterator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class ClearModelsCommand
|
||||
*
|
||||
* @category ClearModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
* @internal
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
class ClearModelsCommand extends AbstractModelsProcessorCommand
|
||||
{
|
||||
/**
|
||||
* Sets description and help for a command.
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('models:clear')
|
||||
->setDescription('Removes all generated models, leaves only empty directory behind.')
|
||||
->setHelp('Use this command if you want to remove existing model cache.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$target = Utils::getModelsCacheDirectory();
|
||||
|
||||
if (!is_dir($target)) {
|
||||
$output->writeln('<error>The target directory does not exist.</error>');
|
||||
$output->writeln('<info>You can safely use "bin/console models:generate" to generate models.</info>');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('Cleaning up <fg=magenta>"%s"</>...', $target));
|
||||
|
||||
if (self::isVerbose($output)) {
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
$checksumFile = implode(DIRECTORY_SEPARATOR, [$target, 'checksum.json']);
|
||||
$models = new PhpFilesIterator($target);
|
||||
|
||||
foreach ($models as $model) {
|
||||
if (file_exists($model['file'])) {
|
||||
unlink($model['file']);
|
||||
|
||||
if (self::isVerbose($output)) {
|
||||
$output->writeln(sprintf('- Removed <fg=magenta>"%s"</>', $model['file']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists($checksumFile)) {
|
||||
unlink($checksumFile);
|
||||
}
|
||||
|
||||
if (self::isVerbose($output)) {
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
$output->writeln('<fg=black;bg=green> ✓ Done!</>');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
206
src/Command/CompilerPromptCommand.php
Normal file
206
src/Command/CompilerPromptCommand.php
Normal file
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CompilerPromptCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Command;
|
||||
|
||||
use JsonException;
|
||||
use RetailCrm\Api\Component\ComposerLocator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Class CompilerPromptCommand
|
||||
*
|
||||
* @category CompilerPromptCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
class CompilerPromptCommand extends Command
|
||||
{
|
||||
private const PACKAGE_NAME = 'retailcrm/api-client-php';
|
||||
private const COMPILER_PLUGIN = 'civicrm/composer-compile-plugin';
|
||||
|
||||
/**
|
||||
* Sets description and help for a command.
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('compiler:prompt')
|
||||
->setDescription('Enable or disable code generation during client installation & update.')
|
||||
->setHelp(
|
||||
'Use this command to enable or disable automatic code generation.'
|
||||
)->addOption(
|
||||
'revert',
|
||||
'r',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'You will need to run ./vendor/bin/retailcrm-client models:generate -a after each update.',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$composerJson = ComposerLocator::findComposerJson();
|
||||
|
||||
if ('' === $composerJson) {
|
||||
$output->writeln('<fg=black;bg=red> ❌ Cannot find composer.json</>');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
$json = json_decode((string) file_get_contents($composerJson), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (JsonException $exception) {
|
||||
$output->writeln(sprintf('<fg=black;bg=red> ❌ Invalid JSON: %s</>', $exception->getMessage()));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$revert = false !== $input->getOption('revert');
|
||||
|
||||
if ($revert) {
|
||||
static::deactivateAutoCompiler($json);
|
||||
} else {
|
||||
static::activateAutoCompiler($json);
|
||||
static::activatePlugin($json);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = json_encode($json, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
} catch (JsonException $exception) {
|
||||
$output->writeln(sprintf('<fg=black;bg=red> ❌ Cannot encode JSON: %s</>', $exception->getMessage()));
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (false === file_put_contents($composerJson, $result)) {
|
||||
$output->writeln('<fg=black;bg=red> ❌ Cannot write to file.</>');
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'<fg=black;bg=green> ✓ Done, code generation has been %s.</>',
|
||||
$revert ? 'disabled' : 'enabled'
|
||||
));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate plugin in the provided composer.json
|
||||
*
|
||||
* @param array<string, array<string, array<string>|string>> $composerJson
|
||||
*/
|
||||
private static function activatePlugin(array &$composerJson): void
|
||||
{
|
||||
if (!array_key_exists('config', $composerJson)) {
|
||||
$composerJson['config'] = [
|
||||
'allow-plugins' => [
|
||||
static::COMPILER_PLUGIN => true
|
||||
]
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!array_key_exists('allow-plugins', $composerJson['config'])) {
|
||||
$composerJson['config']['allow-plugins'] = [
|
||||
static::COMPILER_PLUGIN => true
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$composerJson['config']['allow-plugins'][static::COMPILER_PLUGIN] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate auto compiler in the provided composer.json
|
||||
*
|
||||
* @param array<string, array<string, array<string>|string>> $composerJson
|
||||
*/
|
||||
private static function activateAutoCompiler(array &$composerJson): void
|
||||
{
|
||||
if (!array_key_exists('extra', $composerJson)) {
|
||||
$composerJson['extra'] = [
|
||||
'compile-mode' => 'whitelist',
|
||||
'compile-whitelist' => [self::PACKAGE_NAME]
|
||||
];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (array_key_exists('compile-mode', $composerJson['extra'])) {
|
||||
if (
|
||||
'prompt' === $composerJson['extra']['compile-mode'] ||
|
||||
'none' === $composerJson['extra']['compile-mode']
|
||||
) {
|
||||
$composerJson['extra']['compile-mode'] = 'whitelist';
|
||||
}
|
||||
|
||||
if ('all' === $composerJson['extra']['compile-mode']) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$composerJson['extra']['compile-mode'] = 'whitelist';
|
||||
|
||||
if (!array_key_exists('compile-whitelist', $composerJson['extra'])) {
|
||||
$composerJson['extra']['compile-whitelist'] = [self::PACKAGE_NAME];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array(self::PACKAGE_NAME, $composerJson['extra']['compile-whitelist'])) {
|
||||
$composerJson['extra']['compile-whitelist'][] = self::PACKAGE_NAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate prompt in the provided composer.json
|
||||
*
|
||||
* @param array<string, array<string>> $composerJson
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
private static function deactivateAutoCompiler(array &$composerJson): void
|
||||
{
|
||||
if (!array_key_exists('extra', $composerJson)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
array_key_exists('compile-whitelist', $composerJson['extra']) &&
|
||||
null !== $composerJson['extra']['compile-whitelist']
|
||||
) {
|
||||
$composerJson['extra']['compile-whitelist'] = array_filter(
|
||||
$composerJson['extra']['compile-whitelist'],
|
||||
static function (string $item) {
|
||||
return $item !== self::PACKAGE_NAME;
|
||||
}
|
||||
);
|
||||
|
||||
if (0 === count($composerJson['extra']['compile-whitelist'])) {
|
||||
unset($composerJson['extra']['compile-whitelist']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
103
src/Command/GenerateModelsCommand.php
Normal file
103
src/Command/GenerateModelsCommand.php
Normal file
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category GenerateModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Command;
|
||||
|
||||
use RetailCrm\Api\Component\ModelsGenerator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Class GenerateModelsCommand
|
||||
*
|
||||
* @category GenerateModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
* @internal
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*
|
||||
* @see There is no need to refactor generator into separate service. Its logic won't be used anywhere else.
|
||||
*/
|
||||
class GenerateModelsCommand extends AbstractModelsProcessorCommand
|
||||
{
|
||||
/**
|
||||
* Sets description and help for a command.
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('models:generate')
|
||||
->setDescription('Converts all JMS models to static (de)serialization code.')
|
||||
->setHelp('Use this command after making any changes to the models.')
|
||||
->addOption(
|
||||
'all',
|
||||
'a',
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'Generate cache for all models instead of changed models only.',
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$verbose = static::isVerbose($output);
|
||||
$generator = new ModelsGenerator(false !== $input->getOption('all'));
|
||||
|
||||
$output->writeln('Preparing a list of models to generate cache files...');
|
||||
$output->writeln(
|
||||
'<options=bold>Note:</> Request models will be omitted ' .
|
||||
'because they are being handled by FormEncoder.'
|
||||
);
|
||||
$output->writeln('');
|
||||
|
||||
$generator->loadModelsList();
|
||||
|
||||
if ($verbose) {
|
||||
foreach ($generator->getModels() as $model) {
|
||||
$output->writeln(sprintf('- Added <fg=magenta>%s</>', $model));
|
||||
}
|
||||
}
|
||||
|
||||
if ($verbose && count($generator->getModels()) > 0) {
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
if (count($generator->getModels()) === 0) {
|
||||
$output->writeln('<info>No changes were found; skipping generation...</info>');
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
try {
|
||||
$generator->generate();
|
||||
} catch (\Throwable $throwable) {
|
||||
$styled = new SymfonyStyle($input, $output);
|
||||
$styled->error($throwable->getMessage());
|
||||
$styled->writeln($throwable->getTraceAsString());
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'<fg=black;bg=green> ✓ Done, generated code for %d models.</>',
|
||||
count($generator->getModels())
|
||||
));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
57
src/Command/VerifyModelsCommand.php
Normal file
57
src/Command/VerifyModelsCommand.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category VerifyModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Command;
|
||||
|
||||
use RetailCrm\Api\Component\Serializer\ModelsChecksumGenerator;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Class VerifyModelsCommand
|
||||
*
|
||||
* @category VerifyModelsCommand
|
||||
* @package RetailCrm\Api\Command
|
||||
* @internal
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
|
||||
*/
|
||||
class VerifyModelsCommand extends AbstractModelsProcessorCommand
|
||||
{
|
||||
/**
|
||||
* Sets description and help for a command.
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setName('models:verify')
|
||||
->setDescription('Verify models cache. This command will fail if model cache is not up-to-date.')
|
||||
->setHelp('Use this command if you want to check existing model cache.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output
|
||||
*
|
||||
* @return int
|
||||
* @throws \JsonException
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if (ModelsChecksumGenerator::verifyChecksum()) {
|
||||
$io->success("Models are up to date.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->error("Outdated models! Run \"models:generate\" command to fix that.");
|
||||
return -1;
|
||||
}
|
||||
}
|
97
src/Component/ComposerLocator.php
Normal file
97
src/Component/ComposerLocator.php
Normal file
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ComposerLocator
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
/**
|
||||
* Class ComposerLocator
|
||||
*
|
||||
* @category ComposerLocator
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
class ComposerLocator
|
||||
{
|
||||
/**
|
||||
* Locate Composer autoloader.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function findAutoloader(): string
|
||||
{
|
||||
$counter = 0;
|
||||
$dir = static::getBaseDirectory();
|
||||
|
||||
for (;;) {
|
||||
if (file_exists($dir . '/autoload.php')) {
|
||||
return $dir . '/autoload.php';
|
||||
}
|
||||
|
||||
if (file_exists($dir . '/vendor/autoload.php')) {
|
||||
return $dir . '/vendor/autoload.php';
|
||||
}
|
||||
|
||||
$counter++;
|
||||
$dir = dirname($dir);
|
||||
|
||||
if (5 < $counter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate `composer.json`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function findComposerJson(): string
|
||||
{
|
||||
$counter = 0;
|
||||
$dir = static::getBaseDirectory();
|
||||
|
||||
for (;;) {
|
||||
$fileName = implode(DIRECTORY_SEPARATOR, [$dir, 'composer.json']);
|
||||
|
||||
if (file_exists($fileName) && static::getPackageComposerJson() !== $fileName) {
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
$counter++;
|
||||
$dir = dirname($dir);
|
||||
|
||||
if (2 < $counter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getBaseDirectory(): string
|
||||
{
|
||||
$cwd = getcwd();
|
||||
|
||||
return false === $cwd ? (string) realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..'])) : $cwd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns full path to the composer.json of this package.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getPackageComposerJson(): string
|
||||
{
|
||||
return (string) realpath(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', '..', 'composer.json']));
|
||||
}
|
||||
}
|
80
src/Component/CustomApiMethod.php
Normal file
80
src/Component/CustomApiMethod.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use RetailCrm\Api\Exception\Client\HandlerException;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
|
||||
/**
|
||||
* Class CustomApiMethod
|
||||
*
|
||||
* This class can be used to implement custom methods without any hassle. It is useful if you don't want to
|
||||
* do anything besides sending the request and reading the response.
|
||||
*
|
||||
* @see \RetailCrm\Api\ResourceGroup\CustomMethods::register() for the usage example.
|
||||
*
|
||||
* @category CustomApiMethod
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
class CustomApiMethod
|
||||
{
|
||||
/** @var string */
|
||||
protected $method;
|
||||
|
||||
/** @var string */
|
||||
protected $route;
|
||||
|
||||
/** @var bool */
|
||||
protected $rawRouteUri;
|
||||
|
||||
/**
|
||||
* Instantiates new instance of the CustomApiMethod.
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
*/
|
||||
public function __construct(string $method, string $route)
|
||||
{
|
||||
$this->method = $method;
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use provided route as if it was full URL.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function useRouteAsUri(): self
|
||||
{
|
||||
$this->rawRouteUri = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request, returns the response.
|
||||
*
|
||||
* @param \RetailCrm\Api\Interfaces\RequestSenderInterface $sender
|
||||
* @param array<int|string, mixed>|object $data
|
||||
*
|
||||
* @return array<int|string, mixed>|mixed
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
*/
|
||||
public function __invoke(RequestSenderInterface $sender, $data = [])
|
||||
{
|
||||
if (!is_array($data)) {
|
||||
throw new HandlerException(__CLASS__ . ' only supports array data');
|
||||
}
|
||||
|
||||
return $sender->send($this->method, $this->rawRouteUri ? $this->route : $sender->route($this->route), $data);
|
||||
}
|
||||
}
|
130
src/Component/FilesIteratorChecksumGenerator.php
Normal file
130
src/Component/FilesIteratorChecksumGenerator.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category FilesIteratorChecksumGenerator
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use Iterator;
|
||||
|
||||
/**
|
||||
* Class FilesIteratorChecksumGenerator
|
||||
*
|
||||
* @category FilesIteratorChecksumGenerator
|
||||
* @package RetailCrm\Api\Component
|
||||
* @internal
|
||||
*/
|
||||
class FilesIteratorChecksumGenerator
|
||||
{
|
||||
/** @var Iterator<mixed> */
|
||||
private $iterator;
|
||||
|
||||
/** @var callable */
|
||||
private $fileNameAccessor;
|
||||
|
||||
/** @var callable */
|
||||
private $keyTransformer;
|
||||
|
||||
/** @var callable */
|
||||
private $hashFunc;
|
||||
|
||||
/**
|
||||
* FilesIteratorChecksumGenerator constructor.
|
||||
*
|
||||
* @param Iterator<mixed> $iterator
|
||||
*/
|
||||
public function __construct(Iterator $iterator)
|
||||
{
|
||||
$this->iterator = $iterator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getFileNameAccessor(): callable
|
||||
{
|
||||
return $this->fileNameAccessor ?? static function ($fileName) {
|
||||
return (string) $fileName;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $fileNameAccessor
|
||||
*
|
||||
* @return FilesIteratorChecksumGenerator
|
||||
*/
|
||||
public function setFileNameAccessor(callable $fileNameAccessor): FilesIteratorChecksumGenerator
|
||||
{
|
||||
$this->fileNameAccessor = $fileNameAccessor;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getKeyTransformer(): callable
|
||||
{
|
||||
return $this->keyTransformer ?? static function ($fileName) {
|
||||
return (string) $fileName;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This callable will be used to provide key for the hashes array.
|
||||
*
|
||||
* @param callable $keyTransformer
|
||||
*
|
||||
* @return FilesIteratorChecksumGenerator
|
||||
*/
|
||||
public function setKeyTransformer(callable $keyTransformer): FilesIteratorChecksumGenerator
|
||||
{
|
||||
$this->keyTransformer = $keyTransformer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable
|
||||
*/
|
||||
public function getHashFunc(): callable
|
||||
{
|
||||
return $this->hashFunc ?? static function ($fileName) {
|
||||
$contents = preg_replace('/\r|\n|\r\n/m', "\n", (string) file_get_contents($fileName));
|
||||
return md5($contents ?? '');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will receive file name and should return file hash.
|
||||
*
|
||||
* @param callable $hashFunc
|
||||
*
|
||||
* @return FilesIteratorChecksumGenerator
|
||||
*/
|
||||
public function setHashFunc(callable $hashFunc): FilesIteratorChecksumGenerator
|
||||
{
|
||||
$this->hashFunc = $hashFunc;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate hash string from the contents of a directory.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function generateHashes(): array
|
||||
{
|
||||
$hashes = [];
|
||||
|
||||
foreach ($this->iterator as $item) {
|
||||
$fileName = $this->getFileNameAccessor()($item);
|
||||
$hashes[(string) $this->getKeyTransformer()($item)] = $this->getHashFunc()($fileName);
|
||||
}
|
||||
|
||||
return $hashes;
|
||||
}
|
||||
}
|
131
src/Component/FormData/FormEncoder.php
Normal file
131
src/Component/FormData/FormEncoder.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category FormEncoder
|
||||
* @package RetailCrm\Api\Component\FormData
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\PostSerialize;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\StrategyFactory;
|
||||
use RetailCrm\Api\Interfaces\FormEncoderInterface;
|
||||
|
||||
/**
|
||||
* Class FormEncoder
|
||||
*
|
||||
* FormEncoder is a vital part of the library. Our API expects form-data for all requests, but some fields may contain
|
||||
* JSON data (for example, `/api/v5/customers/create` method works like that). FormEncoder is our custom serializer that
|
||||
* converts request instances to form-data and uses Liip serializer under the hood to fill some fields with JSON data.
|
||||
*
|
||||
* @see https://docs.retailcrm.ru/Developers/API/APIVersions/APIv5#post--api-v5-customers-create
|
||||
*
|
||||
* @category FormEncoder
|
||||
* @package RetailCrm\Api\Component\FormData
|
||||
*/
|
||||
class FormEncoder implements FormEncoderInterface
|
||||
{
|
||||
/** @var \Doctrine\Common\Annotations\Reader */
|
||||
private $annotationReader;
|
||||
|
||||
/** @var \Liip\Serializer\SerializerInterface */
|
||||
private $serializer;
|
||||
|
||||
/**
|
||||
* FormEncoder constructor.
|
||||
*
|
||||
* @param \Liip\Serializer\SerializerInterface $serializer
|
||||
* @param \Doctrine\Common\Annotations\Reader|null $annotationReader
|
||||
*/
|
||||
public function __construct(SerializerInterface $serializer, ?Reader $annotationReader = null)
|
||||
{
|
||||
$this->serializer = $serializer;
|
||||
$this->annotationReader = $annotationReader ?: new AnnotationReader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes provided object into a form data
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param string $type
|
||||
*
|
||||
* @return string
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function encode($object, string $type = ''): string
|
||||
{
|
||||
return http_build_query($this->encodeArray($object, $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes provided object into an array
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param string $type
|
||||
*
|
||||
* @return array<mixed>
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function encodeArray($object, string $type = ''): array
|
||||
{
|
||||
$type = empty($type) ? gettype($object) : $type;
|
||||
$result = (array) StrategyFactory::encodeStrategyByType(
|
||||
$type,
|
||||
$object,
|
||||
$this->annotationReader,
|
||||
$this->serializer
|
||||
)->encode($object, null);
|
||||
|
||||
return $this->processPostSerialize($object, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns underlying serializer instance.
|
||||
*
|
||||
* @return \Liip\Serializer\SerializerInterface
|
||||
*/
|
||||
public function getSerializer(): SerializerInterface
|
||||
{
|
||||
return $this->serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process post deserialize callback
|
||||
*
|
||||
* @param mixed $object
|
||||
* @param mixed[] $result
|
||||
*
|
||||
* @return mixed[]
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function processPostSerialize($object, array $result): array
|
||||
{
|
||||
$class = get_class($object);
|
||||
|
||||
if (false !== $object) {
|
||||
try {
|
||||
$reflection = new ReflectionClass($class);
|
||||
} catch (ReflectionException $e) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
foreach ($reflection->getMethods() as $method) {
|
||||
$postDeserialize = $this->annotationReader
|
||||
->getMethodAnnotation($method, PostSerialize::class);
|
||||
|
||||
if ($postDeserialize instanceof PostSerialize) {
|
||||
return $method->invokeArgs($object, [$result]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
45
src/Component/FormData/Mapping/Accessor.php
Normal file
45
src/Component/FormData/Mapping/Accessor.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Accessor
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use Doctrine\Common\Annotations\Annotation\Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
|
||||
/**
|
||||
* Class Accessor
|
||||
*
|
||||
* @category Accessor
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Attributes(
|
||||
* @Attribute("getter", required=false, type="string"),
|
||||
* @Attribute("setter", required=false, type="string")
|
||||
* )
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class Accessor
|
||||
{
|
||||
/**
|
||||
* Property getter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $getter;
|
||||
|
||||
/**
|
||||
* Property setter
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $setter;
|
||||
}
|
26
src/Component/FormData/Mapping/JsonField.php
Normal file
26
src/Component/FormData/Mapping/JsonField.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category JsonField
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class JsonField
|
||||
*
|
||||
* @category JsonField
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class JsonField
|
||||
{
|
||||
}
|
26
src/Component/FormData/Mapping/NoTransform.php
Normal file
26
src/Component/FormData/Mapping/NoTransform.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category NoTransform
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class NoTransform
|
||||
*
|
||||
* @category NoTransform
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class NoTransform
|
||||
{
|
||||
}
|
26
src/Component/FormData/Mapping/PostDeserialize.php
Normal file
26
src/Component/FormData/Mapping/PostDeserialize.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category PostDeserialize
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class PostDeserialize
|
||||
*
|
||||
* @category PostDeserialize
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
*/
|
||||
final class PostDeserialize
|
||||
{
|
||||
}
|
26
src/Component/FormData/Mapping/PostSerialize.php
Normal file
26
src/Component/FormData/Mapping/PostSerialize.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category PostSerialize
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class PostSerialize
|
||||
*
|
||||
* @category PostSerialize
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Target({"METHOD"})
|
||||
*/
|
||||
final class PostSerialize
|
||||
{
|
||||
}
|
37
src/Component/FormData/Mapping/SerializedName.php
Normal file
37
src/Component/FormData/Mapping/SerializedName.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SerializedName
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use Doctrine\Common\Annotations\Annotation\Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
|
||||
/**
|
||||
* Class SerializedName
|
||||
*
|
||||
* @category SerializedName
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Attributes(
|
||||
* @Attribute("name", required=true, type="string")
|
||||
* )
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class SerializedName
|
||||
{
|
||||
/**
|
||||
* Property name in result JSON
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
}
|
37
src/Component/FormData/Mapping/Type.php
Normal file
37
src/Component/FormData/Mapping/Type.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Type
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Mapping;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use Doctrine\Common\Annotations\Annotation\Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\Attributes;
|
||||
|
||||
/**
|
||||
* Class Type
|
||||
*
|
||||
* @category Type
|
||||
* @package RetailCrm\Api\Component\FormData\Mapping
|
||||
*
|
||||
* @Annotation
|
||||
* @Attributes(
|
||||
* @Attribute("type", required=false, type="string")
|
||||
* )
|
||||
* @Target({"PROPERTY","ANNOTATION"})
|
||||
*/
|
||||
final class Type
|
||||
{
|
||||
/**
|
||||
* Property type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
}
|
73
src/Component/FormData/PropertyAnnotations.php
Normal file
73
src/Component/FormData/PropertyAnnotations.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category PropertyAnnotations
|
||||
* @package RetailCrm\Api\Component\FormData
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData;
|
||||
|
||||
use RetailCrm\Api\Component\FormData\Mapping\Accessor;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\JsonField;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\SerializedName;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\Type;
|
||||
|
||||
/**
|
||||
* Class PropertyAnnotations
|
||||
*
|
||||
* @category PropertyAnnotations
|
||||
* @package RetailCrm\Api\Component\FormData
|
||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||
* @license https://retailcrm.ru Proprietary
|
||||
* @link http://retailcrm.ru
|
||||
* @see https://help.retailcrm.ru
|
||||
*/
|
||||
class PropertyAnnotations
|
||||
{
|
||||
/**
|
||||
* @var SerializedName|null
|
||||
*/
|
||||
public $serializedName;
|
||||
|
||||
/**
|
||||
* @var Accessor|null
|
||||
*/
|
||||
public $accessor;
|
||||
|
||||
/**
|
||||
* @var Type|null
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var JsonField|null
|
||||
*/
|
||||
public $jsonField;
|
||||
|
||||
/**
|
||||
* PropertyAnnotations constructor.
|
||||
*
|
||||
* @param object[] $annotations
|
||||
*/
|
||||
public function __construct(array $annotations = [])
|
||||
{
|
||||
foreach ($annotations as $annotation) {
|
||||
switch (get_class($annotation)) {
|
||||
case Type::class:
|
||||
$this->type = $annotation;
|
||||
break;
|
||||
case SerializedName::class:
|
||||
$this->serializedName = $annotation;
|
||||
break;
|
||||
case Accessor::class:
|
||||
$this->accessor = $annotation;
|
||||
break;
|
||||
case JsonField::class:
|
||||
$this->jsonField = $annotation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category AbstractEncodeStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractEncodeStrategy
|
||||
*
|
||||
* @category AbstractEncodeStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
abstract class AbstractEncodeStrategy implements EncodeStrategyInterface
|
||||
{
|
||||
/** @var \Doctrine\Common\Annotations\Reader */
|
||||
protected $annotationReader;
|
||||
|
||||
/** @var string $innerType */
|
||||
protected $innerType;
|
||||
|
||||
/** @var \Liip\Serializer\SerializerInterface */
|
||||
protected $liipSerializer;
|
||||
|
||||
/**
|
||||
* AbstractEncodeStrategy constructor.
|
||||
*
|
||||
* @param \Doctrine\Common\Annotations\Reader $annotationReader
|
||||
* @param \Liip\Serializer\SerializerInterface $liipSerializer
|
||||
*/
|
||||
public function __construct(Reader $annotationReader, SerializerInterface $liipSerializer)
|
||||
{
|
||||
$this->annotationReader = $annotationReader;
|
||||
$this->liipSerializer = $liipSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets inner type for types like array<key, value> and \DateTime<format>
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface
|
||||
*/
|
||||
public function setInnerType(string $type): EncodeStrategyInterface
|
||||
{
|
||||
$this->innerType = $type;
|
||||
return $this;
|
||||
}
|
||||
}
|
34
src/Component/FormData/Strategy/Encode/DateTimeStrategy.php
Normal file
34
src/Component/FormData/Strategy/Encode/DateTimeStrategy.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category DateTimeStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use DateTime;
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
|
||||
/**
|
||||
* Class DateTimeStrategy
|
||||
*
|
||||
* @category DateTimeStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
class DateTimeStrategy extends AbstractEncodeStrategy
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations): ?string
|
||||
{
|
||||
if ($value instanceof DateTime) {
|
||||
return $value->format($this->innerType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category Integration
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
* @author RetailCRM <integration@retailcrm.ru>
|
||||
* @license MIT
|
||||
* @link http://retailcrm.ru
|
||||
* @see http://retailcrm.ru/docs
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
|
||||
/**
|
||||
* Interface EncodeStrategyInterface
|
||||
*
|
||||
* @internal
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
interface EncodeStrategyInterface
|
||||
{
|
||||
/**
|
||||
* Serialize value
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param \RetailCrm\Api\Component\FormData\PropertyAnnotations|null $annotations
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations);
|
||||
|
||||
/**
|
||||
* Sets inner type for types like array<key, value> and \DateTime<format>
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface
|
||||
*/
|
||||
public function setInnerType(string $type): EncodeStrategyInterface;
|
||||
}
|
117
src/Component/FormData/Strategy/Encode/EntityStrategy.php
Normal file
117
src/Component/FormData/Strategy/Encode/EntityStrategy.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category EntityStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use JMS\Serializer\SerializerInterface;
|
||||
use ReflectionClass;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\JsonField;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\NoTransform;
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\Accessor;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\SerializedName;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\Type;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\StrategyFactory;
|
||||
|
||||
/**
|
||||
* Class EntityStrategy
|
||||
*
|
||||
* @category EntityStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
class EntityStrategy extends AbstractEncodeStrategy
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return mixed[]|string|null
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations = null)
|
||||
{
|
||||
if (empty($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (null !== $annotations && $annotations->jsonField instanceof JsonField) {
|
||||
return $this->liipSerializer->serialize($value, 'json');
|
||||
}
|
||||
|
||||
$result = [];
|
||||
$reflection = new ReflectionClass(get_class($value));
|
||||
|
||||
if (!$reflection->isUserDefined()) {
|
||||
return (array) $value;
|
||||
}
|
||||
|
||||
foreach ($reflection->getProperties() as $property) {
|
||||
$this->encodeProperty($value, $property, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $object
|
||||
* @param \ReflectionProperty $property
|
||||
* @param mixed[] $result
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
protected function encodeProperty($object, \ReflectionProperty $property, array &$result): void
|
||||
{
|
||||
$annotations = new PropertyAnnotations($this->annotationReader->getPropertyAnnotations($property));
|
||||
|
||||
if (!($annotations->serializedName instanceof SerializedName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($annotations->accessor instanceof Accessor && !empty($annotations->accessor->getter)) {
|
||||
$value = $object->{$annotations->accessor->getter}();
|
||||
} else {
|
||||
$property->setAccessible(true);
|
||||
$value = $property->getValue($object);
|
||||
}
|
||||
|
||||
if ($this->isNoTransform($property)) {
|
||||
$result[$annotations->serializedName->name] = $value;
|
||||
} elseif ($annotations->type instanceof Type) {
|
||||
$result[$annotations->serializedName->name] =
|
||||
StrategyFactory::encodeStrategyByType(
|
||||
$annotations->type->type,
|
||||
$value,
|
||||
$this->annotationReader,
|
||||
$this->liipSerializer
|
||||
)->encode($value, $annotations);
|
||||
} else {
|
||||
$result[$annotations->serializedName->name] =
|
||||
StrategyFactory::encodeStrategyByType(
|
||||
gettype($value),
|
||||
$value,
|
||||
$this->annotationReader,
|
||||
$this->liipSerializer
|
||||
)->encode($value, $annotations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if NoTransform annotation was used
|
||||
*
|
||||
* @param \ReflectionProperty $property
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isNoTransform(\ReflectionProperty $property): bool
|
||||
{
|
||||
$isNoTransform = $this->annotationReader->getPropertyAnnotation($property, NoTransform::class);
|
||||
|
||||
return $isNoTransform instanceof NoTransform;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category SimpleTypeStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use RetailCrm\Api\Component\FormData\Mapping\JsonField;
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\StrategyFactory;
|
||||
|
||||
/**
|
||||
* Class SimpleTypeStrategy
|
||||
*
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
class SimpleTypeStrategy extends AbstractEncodeStrategy
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations)
|
||||
{
|
||||
if (null !== $annotations && $annotations->jsonField instanceof JsonField && !empty($value)) {
|
||||
return $this->liipSerializer->serialize($value, 'json');
|
||||
}
|
||||
|
||||
return $this->encodeValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode simple value.
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool|float|int|mixed[]|string|null
|
||||
*/
|
||||
private function encodeValue($value)
|
||||
{
|
||||
switch (gettype($value)) {
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
return (bool) $value;
|
||||
case 'int':
|
||||
case 'integer':
|
||||
return (int) $value;
|
||||
case 'float':
|
||||
return (float) $value;
|
||||
case 'double':
|
||||
return (double) $value;
|
||||
case 'string':
|
||||
return (string) $value;
|
||||
default:
|
||||
return $this->encodeDefault($value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return mixed[]|null
|
||||
*/
|
||||
private function encodeDefault($value): ?array
|
||||
{
|
||||
if (is_iterable($value)) {
|
||||
$result = [];
|
||||
|
||||
foreach ($value as $key => $item) {
|
||||
$result[$key] = StrategyFactory::encodeStrategyByType(
|
||||
gettype($item),
|
||||
$item,
|
||||
$this->annotationReader,
|
||||
$this->liipSerializer
|
||||
)->encode($item, new PropertyAnnotations());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category StreamInterfaceStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
|
||||
/**
|
||||
* Class StreamInterfaceStrategy
|
||||
*
|
||||
* @category StreamInterfaceStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
class StreamInterfaceStrategy extends AbstractEncodeStrategy
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations): ?string
|
||||
{
|
||||
if ($value instanceof StreamInterface) {
|
||||
if ($value->isSeekable()) {
|
||||
$value->seek(0);
|
||||
}
|
||||
|
||||
return $value->getContents();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
121
src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php
Normal file
121
src/Component/FormData/Strategy/Encode/TypedArrayStrategy.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category TypedArrayStrategy
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use RetailCrm\Api\Component\FormData\Mapping\JsonField;
|
||||
use RetailCrm\Api\Component\FormData\PropertyAnnotations;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\StrategyFactory;
|
||||
|
||||
/**
|
||||
* Class TypedArrayStrategy
|
||||
*
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy\Encode
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
class TypedArrayStrategy extends AbstractEncodeStrategy
|
||||
{
|
||||
/** @var string */
|
||||
private static $innerTypesMatcher = '/^([a-z]+)\s*\,?\s*(.+?\>)/m';
|
||||
|
||||
/** @var \RetailCrm\Api\Component\FormData\Strategy\Encode\SimpleTypeStrategy */
|
||||
private $simpleStrategy;
|
||||
|
||||
/**
|
||||
* TypedArrayStrategy constructor.
|
||||
*
|
||||
* @param \Doctrine\Common\Annotations\Reader $annotationReader
|
||||
* @param \Liip\Serializer\SerializerInterface $liipSerializer
|
||||
*/
|
||||
public function __construct(Reader $annotationReader, SerializerInterface $liipSerializer)
|
||||
{
|
||||
parent::__construct($annotationReader, $liipSerializer);
|
||||
|
||||
$this->simpleStrategy = new SimpleTypeStrategy($this->annotationReader, $this->liipSerializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param \RetailCrm\Api\Component\FormData\PropertyAnnotations|null $annotations
|
||||
*
|
||||
* @return mixed[]|mixed
|
||||
*/
|
||||
public function encode($value, ?PropertyAnnotations $annotations = null)
|
||||
{
|
||||
if (!is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (strpos($this->innerType, ',') !== false) {
|
||||
[$keyType, $valueType] = static::getInnerTypes($this->innerType);
|
||||
|
||||
if ('' === $keyType && '' === $valueType) {
|
||||
$valueType = $this->innerType;
|
||||
}
|
||||
} else {
|
||||
$valueType = $this->innerType;
|
||||
}
|
||||
|
||||
if (null !== $annotations && $annotations->jsonField instanceof JsonField && !empty($value)) {
|
||||
return $this->liipSerializer->serialize($value, 'json');
|
||||
}
|
||||
|
||||
return $this->encodeRegularArray($value, $valueType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode regular typed array.
|
||||
*
|
||||
* @param array<string|int, mixed> $value
|
||||
* @param string $valueType
|
||||
*
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
private function encodeRegularArray(array $value, string $valueType): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (array_keys($value) as $key) {
|
||||
$result[$this->simpleStrategy->encode($key, new PropertyAnnotations())]
|
||||
= StrategyFactory::encodeStrategyByType(
|
||||
$valueType,
|
||||
$value[$key],
|
||||
$this->annotationReader,
|
||||
$this->liipSerializer
|
||||
)->encode($value[$key], new PropertyAnnotations());
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns inner types for array with typed key (example: array<string, DateTime<Y m d H i s>>).
|
||||
*
|
||||
* @param string $innerType
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getInnerTypes(string $innerType): array
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
preg_match_all(static::$innerTypesMatcher, $innerType, $matches, PREG_SET_ORDER, 0);
|
||||
|
||||
if (empty($matches)) {
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
$matches = $matches[0];
|
||||
|
||||
return [trim($matches[1]), trim($matches[2])];
|
||||
}
|
||||
}
|
159
src/Component/FormData/Strategy/StrategyFactory.php
Normal file
159
src/Component/FormData/Strategy/StrategyFactory.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category StrategyFactory
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component\FormData\Strategy;
|
||||
|
||||
use DateTime;
|
||||
use Doctrine\Common\Annotations\Reader;
|
||||
use Liip\Serializer\SerializerInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\Encode;
|
||||
use RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface;
|
||||
|
||||
/**
|
||||
* Class StrategyFactory
|
||||
*
|
||||
* @category StrategyFactory
|
||||
* @package RetailCrm\Api\Component\FormData\Strategy
|
||||
*/
|
||||
class StrategyFactory
|
||||
{
|
||||
/** @var string */
|
||||
private const TYPED_MATCHER = '/^\\\\?([a-zA-Z0-9_]+)\s*\<(.+)\>$/m';
|
||||
|
||||
/** @var string[] $simpleTypes */
|
||||
private static $simpleTypes = [
|
||||
'bool',
|
||||
'boolean',
|
||||
'int',
|
||||
'integer',
|
||||
'float',
|
||||
'double',
|
||||
'string',
|
||||
'array'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns encode strategy for provided type
|
||||
*
|
||||
* @param string $dataType
|
||||
* @param mixed $value
|
||||
* @param \Doctrine\Common\Annotations\Reader $annotationReader
|
||||
* @param \Liip\Serializer\SerializerInterface $serializer
|
||||
*
|
||||
* @return \RetailCrm\Api\Component\FormData\Strategy\Encode\EncodeStrategyInterface
|
||||
*/
|
||||
public static function encodeStrategyByType(
|
||||
string $dataType,
|
||||
$value,
|
||||
Reader $annotationReader,
|
||||
SerializerInterface $serializer
|
||||
): EncodeStrategyInterface {
|
||||
if (in_array($dataType, static::$simpleTypes)) {
|
||||
return new Encode\SimpleTypeStrategy($annotationReader, $serializer);
|
||||
}
|
||||
|
||||
if (static::isDateTime($dataType)) {
|
||||
return (new Encode\DateTimeStrategy($annotationReader, $serializer))->setInnerType(DateTime::RFC3339);
|
||||
}
|
||||
|
||||
$arrSubType = static::getArrayInnerTypes($dataType);
|
||||
|
||||
if (!empty($arrSubType)) {
|
||||
return (new Encode\TypedArrayStrategy($annotationReader, $serializer))->setInnerType($arrSubType);
|
||||
}
|
||||
|
||||
$dateTimeFormat = static::getDateTimeFormat($dataType);
|
||||
|
||||
if (!empty($dateTimeFormat)) {
|
||||
return (new Encode\DateTimeStrategy($annotationReader, $serializer))->setInnerType($dateTimeFormat);
|
||||
}
|
||||
|
||||
if ($value instanceof StreamInterface) {
|
||||
return new Encode\StreamInterfaceStrategy($annotationReader, $serializer);
|
||||
}
|
||||
|
||||
return new Encode\EntityStrategy($annotationReader, $serializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if provided type is DateTime
|
||||
*
|
||||
* @param string $dataType
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isDateTime(string $dataType): bool
|
||||
{
|
||||
return strlen($dataType) > 1
|
||||
&& (DateTime::class === $dataType
|
||||
|| ('\\' === $dataType[0] && DateTime::class === substr($dataType, 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array inner type for arrays like array<int, \DateTime<Y-m-d\TH:i:sP>>
|
||||
* For this example, "int, \DateTime<Y-m-d\TH:i:sP>" will be returned.
|
||||
*
|
||||
* Also works for arrays like int[].
|
||||
*
|
||||
* @param string $dataType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getArrayInnerTypes(string $dataType): string
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
preg_match_all(static::TYPED_MATCHER, $dataType, $matches, PREG_SET_ORDER, 0);
|
||||
|
||||
if (empty($matches)) {
|
||||
if (strlen($dataType) > 2 && substr($dataType, -2) === '[]') {
|
||||
return substr($dataType, 0, -2);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($matches[0][1] === 'array') {
|
||||
return $matches[0][2];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns DateTime format. Example: \DateTime<Y-m-d\TH:i:sP>>
|
||||
*
|
||||
* @param string $dataType
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function getDateTimeFormat(string $dataType): string
|
||||
{
|
||||
$matches = [];
|
||||
|
||||
preg_match_all(static::TYPED_MATCHER, $dataType, $matches, PREG_SET_ORDER, 0);
|
||||
|
||||
if (empty($matches)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($matches[0][1] === 'DateTime') {
|
||||
$format = $matches[0][2];
|
||||
|
||||
if (strlen($format) > 2 && $format[0] === "'" && substr($format, -1) === "'") {
|
||||
return substr($format, 1, -1);
|
||||
}
|
||||
|
||||
return $format;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
236
src/Component/ModelsGenerator.php
Normal file
236
src/Component/ModelsGenerator.php
Normal file
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category ModelsGenerator
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Liip\MetadataParser\Builder;
|
||||
use Liip\MetadataParser\ModelParser\RawMetadata\PropertyCollection;
|
||||
use Liip\MetadataParser\Parser;
|
||||
use Liip\MetadataParser\RecursionChecker;
|
||||
use Liip\Serializer\Configuration\GeneratorConfiguration;
|
||||
use Liip\Serializer\Template\Deserialization;
|
||||
use Liip\Serializer\Template\Serialization;
|
||||
use RetailCrm\Api\Component\Serializer\Generator\DeserializerGenerator;
|
||||
use RetailCrm\Api\Component\Serializer\Generator\SerializerGenerator;
|
||||
use RetailCrm\Api\Component\Serializer\ModelsChecksumGenerator;
|
||||
use RetailCrm\Api\Component\Serializer\Parser\JMSParser;
|
||||
use RetailCrm\Api\Component\Serializer\Template\CustomDeserialization;
|
||||
use RetailCrm\Api\Component\Serializer\Template\CustomSerialization;
|
||||
use RetailCrm\Api\Component\Utils as DevUtils;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class ModelsGenerator
|
||||
*
|
||||
* @category ModelsGenerator
|
||||
* @package RetailCrm\Api\Component
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class ModelsGenerator
|
||||
{
|
||||
/**
|
||||
* Request models and filters are being handled by the FormEncoder component.
|
||||
* They don't require caching at all. That's why we should ignore them - there is no need to
|
||||
* waste inodes for the useless cache files.
|
||||
*/
|
||||
private const IGNORED_NAMESPACES = [
|
||||
'RetailCrm\\Api\\Model\\Request',
|
||||
'RetailCrm\\Api\\Model\\Filter'
|
||||
];
|
||||
|
||||
/** @var string[] */
|
||||
private $models;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private $oldChecksums;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private $newChecksums;
|
||||
|
||||
/** @var bool */
|
||||
private $generateAll;
|
||||
|
||||
/**
|
||||
* ModelsGenerator constructor.
|
||||
*
|
||||
* @param bool $generateAll
|
||||
*/
|
||||
public function __construct(bool $generateAll)
|
||||
{
|
||||
$this->generateAll = $generateAll;
|
||||
}
|
||||
|
||||
public function generate(): void
|
||||
{
|
||||
$target = Utils::getModelsCacheDirectory();
|
||||
$this->calculateChecksums();
|
||||
|
||||
if (!is_dir($target)) {
|
||||
static::createDir($target);
|
||||
file_put_contents(implode(DIRECTORY_SEPARATOR, [$target, '.gitkeep']), '');
|
||||
}
|
||||
|
||||
self::generateModelCache($this->models, $target);
|
||||
ModelsChecksumGenerator::saveChecksums($this->newChecksums);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the models present in the library.
|
||||
*/
|
||||
public function loadModelsList(): void
|
||||
{
|
||||
$this->models = [];
|
||||
$classes = new PhpFilesIterator(DevUtils::getModelsDirectory());
|
||||
|
||||
foreach ($classes as $model) {
|
||||
if (!array_key_exists('fqn', $model)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!static::isNamespaceIgnored($model['fqn']) &&
|
||||
$this->shouldGenerateForModel($model['fqn'])
|
||||
) {
|
||||
$this->models[] = $model['fqn'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getModels(): array
|
||||
{
|
||||
return $this->models;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate models checksums
|
||||
*/
|
||||
private function calculateChecksums(): void
|
||||
{
|
||||
$this->newChecksums = ModelsChecksumGenerator::generateChecksums();
|
||||
|
||||
if (!$this->generateAll) {
|
||||
$this->oldChecksums = ModelsChecksumGenerator::getStoredChecksums();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if cache for model should be generated.
|
||||
*
|
||||
* @param string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldGenerateForModel(string $className): bool
|
||||
{
|
||||
if ($this->generateAll) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$serializerFile = SerializerGenerator::buildSerializerFunctionName($className, null, []) . '.php';
|
||||
$deserializerFile = DeserializerGenerator::buildDeserializerFunctionName($className) . '.php';
|
||||
|
||||
if (
|
||||
!is_file(implode(DIRECTORY_SEPARATOR, [Utils::getModelsCacheDirectory(), $serializerFile])) ||
|
||||
!is_file(implode(DIRECTORY_SEPARATOR, [Utils::getModelsCacheDirectory(), $deserializerFile]))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
isset($this->oldChecksums[$className]) &&
|
||||
$this->oldChecksums[$className] !== $this->newChecksums[$className]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate models cache.
|
||||
*
|
||||
* @param string[] $classes
|
||||
* @param string $target
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function generateModelCache(array $classes, string $target): void
|
||||
{
|
||||
if (empty($classes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$configurationArray = [
|
||||
'default_group_combinations' => [],
|
||||
'default_versions' => [],
|
||||
'classes' => [],
|
||||
];
|
||||
|
||||
foreach ($classes as $class) {
|
||||
$configurationArray['classes'][$class] = [];
|
||||
}
|
||||
|
||||
PropertyCollection::useIdenticalNamingStrategy();
|
||||
$configuration = GeneratorConfiguration::createFomArray($configurationArray);
|
||||
$parsers = [new JMSParser(new AnnotationReader())];
|
||||
$builder = new Builder(new Parser($parsers), new RecursionChecker(null, []));
|
||||
|
||||
$marshalGenerator = new SerializerGenerator(
|
||||
new Serialization(),
|
||||
new CustomSerialization(),
|
||||
$configuration,
|
||||
$target
|
||||
);
|
||||
$unmarshalGenerator = new DeserializerGenerator(
|
||||
new Deserialization(),
|
||||
new CustomDeserialization(),
|
||||
$classes,
|
||||
$target
|
||||
);
|
||||
$marshalGenerator->generate($builder);
|
||||
$unmarshalGenerator->generate($builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if models in provided namespace should be ignored.
|
||||
*
|
||||
* @param string $namespace
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function isNamespaceIgnored(string $namespace): bool
|
||||
{
|
||||
foreach (static::IGNORED_NAMESPACES as $ignoredNamespace) {
|
||||
if (false !== strpos($namespace, $ignoredNamespace)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create directory
|
||||
*
|
||||
* @param string $dir
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
private static function createDir(string $dir): void
|
||||
{
|
||||
if (is_dir($dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (false === mkdir($dir, 0777, true) && false === is_dir($dir)) {
|
||||
throw new RuntimeException(sprintf('Could not create directory "%s".', $dir));
|
||||
}
|
||||
}
|
||||
}
|
110
src/Component/PhpFilesIterator.php
Normal file
110
src/Component/PhpFilesIterator.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category PhpFilesIterator
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use Iterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RecursiveRegexIterator;
|
||||
use RegexIterator;
|
||||
|
||||
/**
|
||||
* Class PhpFilesIterator
|
||||
*
|
||||
* @category PhpFilesIterator
|
||||
* @package RetailCrm\Api\Component
|
||||
* @implements Iterator<string|int, array<string, string>>
|
||||
*/
|
||||
class PhpFilesIterator implements Iterator
|
||||
{
|
||||
private const NAMESPACE_MATCHER = '/namespace\s+((?:\\\\{1,2}\w+|\w+\\\\{1,2})(?:\w+\\\\{0,2})+)/m';
|
||||
|
||||
/** @var Iterator<string|int, string|array> */
|
||||
private $parent;
|
||||
|
||||
/**
|
||||
* PhpFilesIterator constructor.
|
||||
*
|
||||
* @param string $directory
|
||||
*/
|
||||
public function __construct(string $directory)
|
||||
{
|
||||
$this->parent = new RegexIterator(
|
||||
new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory)),
|
||||
'/^.+\.php$/i',
|
||||
RecursiveRegexIterator::GET_MATCH
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function current(): array
|
||||
{
|
||||
$matches = [];
|
||||
$file = $this->parent->current();
|
||||
|
||||
if (is_array($file) && count($file) > 0) {
|
||||
$file = $file[0];
|
||||
}
|
||||
|
||||
if (empty($file)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = ['file' => $file];
|
||||
|
||||
preg_match(static::NAMESPACE_MATCHER, (string) file_get_contents($file), $matches);
|
||||
|
||||
if (count($matches) >= 2) {
|
||||
$result['namespace'] = $matches[1];
|
||||
$result['fqn'] = sprintf('%s\\%s', $matches[1], str_ireplace('.php', '', basename($file)));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->parent->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->parent->key();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->parent->valid();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->parent->rewind();
|
||||
}
|
||||
}
|
106
src/Component/RequestSender.php
Normal file
106
src/Component/RequestSender.php
Normal file
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHP version 7.3
|
||||
*
|
||||
* @category RequestSender
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
|
||||
namespace RetailCrm\Api\Component;
|
||||
|
||||
use Psr\Http\Client\ClientExceptionInterface;
|
||||
use Psr\Http\Client\NetworkExceptionInterface;
|
||||
use RetailCrm\Api\Interfaces\RequestSenderInterface;
|
||||
use RetailCrm\Api\ResourceGroup\AbstractApiResourceGroup;
|
||||
use RetailCrm\Api\Traits\BaseUrlAwareTrait;
|
||||
|
||||
/**
|
||||
* Class RequestSender
|
||||
*
|
||||
* @category RequestSender
|
||||
* @package RetailCrm\Api\Component
|
||||
*/
|
||||
class RequestSender extends AbstractApiResourceGroup implements RequestSenderInterface
|
||||
{
|
||||
use BaseUrlAwareTrait {
|
||||
route as private makeRoute;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \RetailCrm\Api\ResourceGroup\AbstractApiResourceGroup $resourceGroup
|
||||
*/
|
||||
public function __construct(AbstractApiResourceGroup $resourceGroup)
|
||||
{
|
||||
parent::__construct(
|
||||
$resourceGroup->baseUrl,
|
||||
$resourceGroup->httpClient,
|
||||
$resourceGroup->requestTransformer,
|
||||
$resourceGroup->responseTransformer,
|
||||
$resourceGroup->eventDispatcher,
|
||||
$resourceGroup->logger
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends custom request to provided route with provided method and body, returns array response.
|
||||
* Request will be put into GET parameters or into POST form-data (depends on method).
|
||||
*
|
||||
* Note: do not remove "useless" exceptions which are marked as "never thrown" by IDE.
|
||||
* PSR-18's ClientInterface doesn't have them in the DocBlock, but, according to PSR-18,
|
||||
* they can be thrown by clients, and therefore should be present here.
|
||||
*
|
||||
* @see https://www.php-fig.org/psr/psr-18/#error-handling
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $route
|
||||
* @param array<int|string, mixed> $requestForm
|
||||
*
|
||||
* @return array<int|string, mixed>
|
||||
* @throws \RetailCrm\Api\Exception\ApiException
|
||||
* @throws \RetailCrm\Api\Exception\ClientException
|
||||
* @throws \RetailCrm\Api\Exception\Client\HandlerException
|
||||
* @throws \RetailCrm\Api\Interfaces\ApiExceptionInterface
|
||||
* @SuppressWarnings(PHPMD.ElseExpression)
|
||||
*/
|
||||
public function send(
|
||||
string $method,
|
||||
string $route,
|
||||
array $requestForm = []
|
||||
): array {
|
||||
$method = strtoupper($method);
|
||||
$psrRequest = $this->requestTransformer->createCustomPsrRequest($method, $route, $requestForm);
|
||||
|
||||
$this->logPsr7Request($psrRequest);
|
||||
|
||||
try {
|
||||
$psrResponse = $this->httpClient->sendRequest($psrRequest);
|
||||
} catch (ClientExceptionInterface | NetworkExceptionInterface $exception) {
|
||||
$this->processPsr18Exception($psrRequest, $exception);
|
||||
}
|
||||
|
||||
if (isset($psrResponse)) {
|
||||
$this->logPsr7Response($psrResponse);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->responseTransformer->createCustomResponse($this->baseUrl, $psrRequest, $psrResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function route(string $route): string
|
||||
{
|
||||
return $this->makeRoute($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function host(): string
|
||||
{
|
||||
return (string) parse_url($this->baseUrl, PHP_URL_HOST);
|
||||
}
|
||||
}
|
21
src/Component/Serializer/Annotation/AccessType.php
Normal file
21
src/Component/Serializer/Annotation/AccessType.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"CLASS", "PROPERTY"})
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class AccessType
|
||||
{
|
||||
/**
|
||||
* @Required
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
}
|
25
src/Component/Serializer/Annotation/Accessor.php
Normal file
25
src/Component/Serializer/Annotation/Accessor.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("PROPERTY")
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class Accessor
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $getter;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $setter;
|
||||
}
|
28
src/Component/Serializer/Annotation/AccessorOrder.php
Normal file
28
src/Component/Serializer/Annotation/AccessorOrder.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* Controls the order of properties in a class.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class AccessorOrder
|
||||
{
|
||||
/**
|
||||
* @Required
|
||||
* @var string
|
||||
*/
|
||||
public $order;
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public $custom = [];
|
||||
}
|
26
src/Component/Serializer/Annotation/Discriminator.php
Normal file
26
src/Component/Serializer/Annotation/Discriminator.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
class Discriminator
|
||||
{
|
||||
/** @var array<string> */
|
||||
public $map;
|
||||
|
||||
/** @var string */
|
||||
public $field = 'type';
|
||||
|
||||
/** @var bool */
|
||||
public $disabled = false;
|
||||
|
||||
/** @var string[] */
|
||||
public $groups = [];
|
||||
}
|
19
src/Component/Serializer/Annotation/Exclude.php
Normal file
19
src/Component/Serializer/Annotation/Exclude.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY", "CLASS", "METHOD", "ANNOTATION"})
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class Exclude
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $if;
|
||||
}
|
54
src/Component/Serializer/Annotation/ExclusionPolicy.php
Normal file
54
src/Component/Serializer/Annotation/ExclusionPolicy.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
use RetailCrm\Api\Component\Serializer\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class ExclusionPolicy
|
||||
{
|
||||
public const NONE = 'NONE';
|
||||
public const ALL = 'ALL';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $policy;
|
||||
|
||||
/**
|
||||
* ExclusionPolicy constructor.
|
||||
*
|
||||
* @param array<string, string> $values
|
||||
*/
|
||||
public function __construct(array $values)
|
||||
{
|
||||
$value = self::NONE;
|
||||
|
||||
if (array_key_exists('value', $values)) {
|
||||
$value = $values['value'];
|
||||
}
|
||||
|
||||
if (array_key_exists('policy', $values)) {
|
||||
$value = $values['policy'];
|
||||
}
|
||||
|
||||
if (!\is_string($value)) {
|
||||
throw new RuntimeException('Exclusion policy value must be of string type.');
|
||||
}
|
||||
|
||||
$value = strtoupper($value);
|
||||
|
||||
if (self::NONE !== $value && self::ALL !== $value) {
|
||||
throw new RuntimeException('Exclusion policy must either be "ALL", or "NONE".');
|
||||
}
|
||||
|
||||
$this->policy = $value;
|
||||
}
|
||||
}
|
19
src/Component/Serializer/Annotation/Expose.php
Normal file
19
src/Component/Serializer/Annotation/Expose.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY", "METHOD", "ANNOTATION"})
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class Expose
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $if;
|
||||
}
|
17
src/Component/Serializer/Annotation/Groups.php
Normal file
17
src/Component/Serializer/Annotation/Groups.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","METHOD","ANNOTATION"})
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class Groups
|
||||
{
|
||||
/** @var array<string> @Required */
|
||||
public $groups;
|
||||
}
|
15
src/Component/Serializer/Annotation/Inline.php
Normal file
15
src/Component/Serializer/Annotation/Inline.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","METHOD","ANNOTATION"})
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class Inline
|
||||
{
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue