Compare commits
364 commits
Author | SHA1 | Date | |
---|---|---|---|
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 | ||
|
118644be43 | ||
|
7f9e8cbb56 | ||
|
1c695eeda7 | ||
|
1387fbca38 | ||
|
4881a416be | ||
|
ecb26bc1ff | ||
|
31b1d9d0f1 | ||
|
9d3e2817fe | ||
|
9fdb2f0ada | ||
|
0b512fa031 | ||
|
5d37588ab2 | ||
|
04520e7993 | ||
|
700b87fba0 | ||
|
aba730295d | ||
|
3a3d0c2658 | ||
|
e09fc4a58f | ||
|
6ee6452748 | ||
|
1cb4074470 | ||
|
741ee9bb1a | ||
|
2098ad3bf2 | ||
|
67e0f4746e | ||
|
de6dbdd87f | ||
|
7ec35c2ad8 | ||
|
d1b26b1663 | ||
|
c7e7ed92cf | ||
|
03466cf0ad | ||
|
b4f3c0efb2 | ||
|
33f34b991e | ||
|
1b5af03644 | ||
|
1319ff063d | ||
|
4eb7a73887 | ||
|
a084f0f183 | ||
|
695b0555a1 | ||
|
4e78faa228 | ||
|
60db8448d7 | ||
|
a217a9786e | ||
|
91955b3ee4 | ||
|
19b483dacb | ||
|
8c029959e5 | ||
|
931e11fbfa | ||
|
0a82ae223d | ||
|
cf536edc2c | ||
|
652938ea89 | ||
|
65eab8845b | ||
|
3c2b25982d | ||
|
ca2b480a26 | ||
|
de8514fe14 | ||
|
5a81516922 | ||
|
a0f583406c | ||
|
5b1e5651e7 | ||
|
a1aa7da86d | ||
|
be29e86d73 | ||
|
8b59f079ef | ||
|
5a9c0bdb7b | ||
|
921dbda756 | ||
|
5effd0cd18 | ||
|
e5b07ed701 | ||
|
b00bbdae91 | ||
|
9eefd8d8ba | ||
|
e15b87fd0f | ||
|
6260236e09 | ||
|
74d41119ed | ||
|
88864dcafe | ||
|
225937f12e | ||
|
a53565c8fd | ||
|
e5215e7fcb | ||
|
c7ab1125c3 | ||
|
7345e7f6cd | ||
|
3fcfa535de | ||
|
b54350ff2f | ||
|
a5e7b5253d | ||
|
66139a5e8e | ||
|
1e61f51ae6 | ||
|
4f678f35d8 |
876 changed files with 88546 additions and 585 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 }}
|
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# Composer files.
|
||||
/vendor
|
||||
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
|
||||
.buildpath
|
||||
.project
|
||||
.swp
|
||||
/nbproject
|
||||
.env
|
||||
|
||||
.docker
|
||||
docker*
|
||||
Makefile
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
309
README.md
309
README.md
|
@ -1,55 +1,286 @@
|
|||
IntaroCRM REST API 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 [IntaroCRM REST API](http://docs.intarocrm.ru/rest-api/).
|
||||
|
||||
Requirements
|
||||
------------
|
||||
# RetailCRM API PHP client
|
||||
|
||||
* PHP version 5.3 and above
|
||||
* PHP-extension CURL
|
||||
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).
|
||||
|
||||
Installation
|
||||
------------
|
||||
# Table of contents
|
||||
|
||||
Add IntaroCRM REST API client in your composer.json:
|
||||
* [Requirements](#requirements)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Examples](#examples)
|
||||
* [Notes](#notes)
|
||||
* [Documentation](doc/index.md)
|
||||
|
||||
```js
|
||||
{
|
||||
"require": {
|
||||
"intarocrm/rest-api-client": "1.2.*"
|
||||
}
|
||||
}
|
||||
## Requirements
|
||||
|
||||
* 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)
|
||||
|
||||
## Installation
|
||||
|
||||
Follow those steps to install the library:
|
||||
|
||||
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:"~6.0"
|
||||
```
|
||||
Usage
|
||||
------------
|
||||
|
||||
### Create API clent class
|
||||
|
||||
``` php
|
||||
|
||||
$crmApiClient = new \IntaroCrm\RestApi(
|
||||
'https://demo.intarocrm.ru',
|
||||
'T9DMPvuNt7FQJMszHUdG8Fkt6xHsqngH'
|
||||
);
|
||||
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,?]
|
||||
```
|
||||
Constructor arguments are:
|
||||
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
|
||||
|
||||
1. Your IntaroCRM acount URL-address
|
||||
2. Your site API Token
|
||||
Allow these packages to compile? ([y]es, [a]lways, [n]o, [l]ist, [h]elp)
|
||||
```
|
||||
|
||||
### Example: get order types list
|
||||
Choose `[a]lways` by typing `a` and pressing Enter.
|
||||
|
||||
``` php
|
||||
**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
|
||||
|
||||
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 {
|
||||
$orderTypes = $crmApiClient->orderTypesList();
|
||||
}
|
||||
catch (\IntaroCrm\Exception\CurlException $e) {
|
||||
//$logger->addError('orderTypesList: connection error');
|
||||
}
|
||||
catch (\IntaroCrm\Exception\ApiException $e) {
|
||||
//$logger->addError('orderTypesList: ' . $e->getMessage());
|
||||
$response = $client->orders->list();
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface and ClientExceptionInterface instance implements __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;
|
||||
}
|
||||
```
|
||||
|
||||
Fetching a specific order by it's ID:
|
||||
|
||||
```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->orders->get(1234, new BySiteRequest(ByIdentifier::ID, 'site'));
|
||||
} catch (ApiExceptionInterface | ClientExceptionInterface $exception) {
|
||||
echo $exception; // Every ApiExceptionInterface instance should implement __toString() method.
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
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.
|
||||
|
|
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();
|
118
composer.json
118
composer.json
|
@ -1,29 +1,121 @@
|
|||
{
|
||||
"name": "intarocrm/rest-api-client",
|
||||
"description": "PHP Client for IntaroCRM REST API",
|
||||
"name": "retailcrm/api-client-php",
|
||||
"description": "PHP client for RetailCRM API",
|
||||
"type": "library",
|
||||
"keywords": ["api", "Intaro CRM", "rest"],
|
||||
"homepage": "http://www.intarocrm.ru/",
|
||||
"keywords": [
|
||||
"API",
|
||||
"RetailCRM",
|
||||
"REST"
|
||||
],
|
||||
"homepage": "http://www.retailcrm.pro/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Kruglov Kirill",
|
||||
"email": "kruglov@intaro.ru",
|
||||
"role": "Developer"
|
||||
"name": "RetailCRM",
|
||||
"email": "support@retailcrm.pro"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.2.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": {
|
||||
"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": { "IntaroCrm\\": "lib/" }
|
||||
"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": "1.0.x-dev"
|
||||
"dev-master": "6.x-dev"
|
||||
},
|
||||
"compile": [
|
||||
{
|
||||
"run": "@composer run-script models"
|
||||
}
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"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)
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace IntaroCrm\Exception;
|
||||
|
||||
class ApiException extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace IntaroCrm\Exception;
|
||||
|
||||
class CurlException extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,521 +0,0 @@
|
|||
<?php
|
||||
namespace IntaroCrm;
|
||||
|
||||
class RestApi
|
||||
{
|
||||
protected $apiUrl;
|
||||
protected $apiKey;
|
||||
protected $apiVersion = '1';
|
||||
|
||||
protected $parameters;
|
||||
|
||||
/**
|
||||
* @param string $crmUrl - адрес CRM
|
||||
* @param string $apiKey - ключ для работы с api
|
||||
*/
|
||||
public function __construct($crmUrl, $apiKey)
|
||||
{
|
||||
$this->apiUrl = $crmUrl.'/api/v'.$this->apiVersion.'/';
|
||||
$this->apiKey = $apiKey;
|
||||
$this->parameters = array('apiKey' => $this->apiKey);
|
||||
}
|
||||
|
||||
/* Методы для работы с заказами */
|
||||
/**
|
||||
* Получение заказа по id
|
||||
*
|
||||
* @param string $id - идентификатор заказа
|
||||
* @param string $by - поиск заказа по id или externalId
|
||||
* @return array - информация о заказе
|
||||
*/
|
||||
public function orderGet($id, $by = 'externalId')
|
||||
{
|
||||
$url = $this->apiUrl.'orders/'.$id;
|
||||
|
||||
if ($by != 'externalId')
|
||||
$this->parameters['by'] = $by;
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание заказа
|
||||
*
|
||||
* @param array $order- информация о заказе
|
||||
* @return array
|
||||
*/
|
||||
public function orderCreate($order)
|
||||
{
|
||||
$dataJson = json_encode($order);
|
||||
$this->parameters['order'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'orders/create';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Изменение заказа
|
||||
*
|
||||
* @param array $order- информация о заказе
|
||||
* @return array
|
||||
*/
|
||||
public function orderEdit($order)
|
||||
{
|
||||
$dataJson = json_encode($order);
|
||||
$this->parameters['order'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'orders/'.$order['externalId'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пакетная загрузка заказов
|
||||
*
|
||||
* @param array $orders - массив заказов
|
||||
* @return array
|
||||
*/
|
||||
public function orderUpload($orders)
|
||||
{
|
||||
$dataJson = json_encode($orders);
|
||||
$this->parameters['orders'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'orders/upload';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
if (is_null($result) && isset($result['uploadedOrders']))
|
||||
return $result['uploadedOrders'];
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление externalId у заказов с переданными id
|
||||
*
|
||||
* @param array $orders- массив, содержащий id и externalId заказа
|
||||
* @return array
|
||||
*/
|
||||
public function orderFixExternalIds($order)
|
||||
{
|
||||
$dataJson = json_encode($order);
|
||||
$this->parameters['orders'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'orders/fix-external-ids';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление заказа
|
||||
*
|
||||
* @param string $id - идентификатор заказа
|
||||
* @param string $by - поиск заказа по id или externalId
|
||||
* @return array
|
||||
*/
|
||||
/*
|
||||
public function orderDelete($id, $by = 'externalId')
|
||||
{
|
||||
$url = $this->apiUrl.'orders/'.$id.'/delete';
|
||||
if ($by != 'externalId')
|
||||
$this->parameters['by'] = $by;
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Получение последних измененных заказов
|
||||
*
|
||||
* @param DateTime $startDate - начальная дата выборки
|
||||
* @param DateTime $endDate - конечная дата
|
||||
* @param int $limit - ограничение на размер выборки
|
||||
* @param int $offset - сдвиг
|
||||
* @return array - массив заказов
|
||||
*/
|
||||
public function orderHistory($startDate = null, $endDate = null, $limit = 100, $offset = 0)
|
||||
{
|
||||
$url = $this->apiUrl.'orders/history';
|
||||
$this->parameters['startDate'] = $startDate;
|
||||
$this->parameters['endDate'] = $endDate;
|
||||
$this->parameters['limit'] = $limit;
|
||||
$this->parameters['offset'] = $offset;
|
||||
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/* Методы для работы с клиентами */
|
||||
/**
|
||||
* Получение клиента по id
|
||||
*
|
||||
* @param string $id - идентификатор
|
||||
* @param string $by - поиск заказа по id или externalId
|
||||
* @return array - информация о клиенте
|
||||
*/
|
||||
public function customerGet($id, $by = 'externalId')
|
||||
{
|
||||
$url = $this->apiUrl.'customers/'.$id;
|
||||
if ($by != 'externalId')
|
||||
$this->parameters['by'] = $by;
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание клиента
|
||||
*
|
||||
* @param array $customer - информация о клиенте
|
||||
* @return array
|
||||
*/
|
||||
public function customerCreate($customer)
|
||||
{
|
||||
$dataJson = json_encode($customer);
|
||||
$this->parameters['customer'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'customers/create';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование клиента
|
||||
*
|
||||
* @param array $customer - информация о клиенте
|
||||
* @return array
|
||||
*/
|
||||
public function customerEdit($customer)
|
||||
{
|
||||
$dataJson = json_encode($customer);
|
||||
$this->parameters['customer'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'customers/'.$customer['externalId'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Пакетная загрузка клиентов
|
||||
*
|
||||
* @param array $customers - массив клиентов
|
||||
* @return array
|
||||
*/
|
||||
public function customerUpload($customers)
|
||||
{
|
||||
$dataJson = json_encode($customers);
|
||||
$this->parameters['customers'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'customers/upload';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
if (is_null($result) && isset($result['uploaded']))
|
||||
return $result['uploaded'];
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление клиента
|
||||
*
|
||||
* @param string $id - идентификатор
|
||||
* @param string $by - поиск заказа по id или externalId
|
||||
* @return array
|
||||
*/
|
||||
/*
|
||||
public function customerDelete($id, $by = 'externalId')
|
||||
{
|
||||
$url = $this->apiUrl.'customers/'.$id.'/delete';
|
||||
if ($by != 'externalId')
|
||||
$this->parameters['by'] = $by;
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Получение списка заказов клиента
|
||||
*
|
||||
* @param string $id - идентификатор клиента
|
||||
* @param string $by - поиск заказа по id или externalId
|
||||
* @param DateTime $startDate - начальная дата выборки
|
||||
* @param DateTime $endDate - конечная дата
|
||||
* @param int $limit - ограничение на размер выборки
|
||||
* @param int $offset - сдвиг
|
||||
* @return array - массив заказов
|
||||
*/
|
||||
public function customerOrdersList($id, $startDate = null, $endDate = null,
|
||||
$limit = 100, $offset = 0, $by = 'externalId')
|
||||
{
|
||||
$url = $this->apiUrl.'customers/'.$id.'/orders';
|
||||
if ($by != 'externalId')
|
||||
$this->parameters['by'] = $by;
|
||||
$this->parameters['startDate'] = $startDate;
|
||||
$this->parameters['endDate'] = $endDate;
|
||||
$this->parameters['limit'] = $limit;
|
||||
$this->parameters['offset'] = $offset;
|
||||
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* Методы для работы со справочниками */
|
||||
/**
|
||||
* Получение списка типов доставки
|
||||
*
|
||||
* @return array - массив типов доставки
|
||||
*/
|
||||
public function deliveryTypesList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/delivery-types';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование типа доставки
|
||||
*
|
||||
* @param array $deliveryType - информация о типе доставки
|
||||
* @return array
|
||||
*/
|
||||
public function deliveryTypeEdit($deliveryType)
|
||||
{
|
||||
$dataJson = json_encode($deliveryType);
|
||||
$this->parameters['deliveryType'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/delivery-types/'.$deliveryType['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получение списка типов оплаты
|
||||
*
|
||||
* @return array - массив типов оплаты
|
||||
*/
|
||||
public function paymentTypesList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/payment-types';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование типа оплаты
|
||||
*
|
||||
* @param array $paymentType - информация о типе оплаты
|
||||
* @return array
|
||||
*/
|
||||
public function paymentTypesEdit($paymentType)
|
||||
{
|
||||
$dataJson = json_encode($paymentType);
|
||||
$this->parameters['paymentType'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/payment-types/'.$paymentType['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получение списка статусов оплаты
|
||||
*
|
||||
* @return array - массив статусов оплаты
|
||||
*/
|
||||
public function paymentStatusesList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/payment-statuses';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование статуса оплаты
|
||||
*
|
||||
* @param array $paymentStatus - информация о статусе оплаты
|
||||
* @return array
|
||||
*/
|
||||
public function paymentStatusesEdit($paymentStatus)
|
||||
{
|
||||
$dataJson = json_encode($paymentStatus);
|
||||
$this->parameters['paymentStatus'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/payment-statuses/'.$paymentStatus['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получение списка типов заказа
|
||||
*
|
||||
* @return array - массив типов заказа
|
||||
*/
|
||||
public function orderTypesList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/order-types';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование типа заказа
|
||||
*
|
||||
* @param array $orderType - информация о типе заказа
|
||||
* @return array
|
||||
*/
|
||||
public function orderTypesEdit($orderType)
|
||||
{
|
||||
$dataJson = json_encode($orderType);
|
||||
$this->parameters['orderType'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/order-types/'.$orderType['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получение списка способов оформления заказа
|
||||
*
|
||||
* @return array - массив способов оформления заказа
|
||||
*/
|
||||
public function orderMethodsList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/order-methods';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование способа оформления заказа
|
||||
*
|
||||
* @param array $orderMethod - информация о способе оформления заказа
|
||||
* @return array
|
||||
*/
|
||||
public function orderMethodsEdit($orderMethod)
|
||||
{
|
||||
$dataJson = json_encode($orderMethod);
|
||||
$this->parameters['orderMethod'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/order-methods/'.$orderMethod['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение списка статусов заказа
|
||||
*
|
||||
* @return array - массив статусов заказа
|
||||
*/
|
||||
public function orderStatusesList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/statuses';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактирование статуса заказа
|
||||
*
|
||||
* @param array $status - информация о статусе заказа
|
||||
* @return array
|
||||
*/
|
||||
public function orderStatusEdit($status)
|
||||
{
|
||||
$dataJson = json_encode($status);
|
||||
$this->parameters['status'] = $dataJson;
|
||||
|
||||
$url = $this->apiUrl.'reference/statuses/'.$status['code'].'/edit';
|
||||
$result = $this->curlRequest($url, 'POST');
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Получение списка групп статусов заказа
|
||||
*
|
||||
* @return array - массив групп статусов заказа
|
||||
*/
|
||||
public function orderStatusGroupsList()
|
||||
{
|
||||
$url = $this->apiUrl.'reference/status-groups';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление статистики
|
||||
*
|
||||
* @return array - статус вып обновления
|
||||
*/
|
||||
public function statisticUpdate()
|
||||
{
|
||||
$url = $this->apiUrl.'statistic/update';
|
||||
$result = $this->curlRequest($url);
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getErrorMessage($response)
|
||||
{
|
||||
$str = '';
|
||||
if (isset($response['message']))
|
||||
$str = $response['message'];
|
||||
elseif (isset($response[0]['message']))
|
||||
$str = $response[0]['message'];
|
||||
elseif (isset($response['error']) && isset($response['error']['message']))
|
||||
$str = $response['error']['message'];
|
||||
elseif (isset($response['errorMsg']))
|
||||
$str = $response['errorMsg'];
|
||||
|
||||
if (isset($response['errors']) && sizeof($response['errors'])) {
|
||||
foreach ($response['errors'] as $error)
|
||||
$str .= '. ' . $error;
|
||||
}
|
||||
|
||||
if (!strlen($str))
|
||||
return 'Application Error';
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
protected function curlRequest($url, $method = 'GET', $format = 'json')
|
||||
{
|
||||
if ($method == 'GET' && !is_null($this->parameters))
|
||||
$url .= '?'.http_build_query($this->parameters);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, FALSE);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);// allow redirects
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // return into a variable
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30); // times out after 30s
|
||||
|
||||
if ($method == 'POST')
|
||||
{
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $this->parameters);
|
||||
}
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
unset($this->parameters);
|
||||
/* Сброс массива с параметрами */
|
||||
$this->parameters = array('apiKey' => $this->apiKey);
|
||||
|
||||
$errno = curl_errno($ch);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($errno)
|
||||
throw new Exception\CurlException($error, $errno);
|
||||
|
||||
$result = json_decode($response, true);
|
||||
|
||||
if ($statusCode >= 400 || isset($result['success']) && $result['success'] === false) {
|
||||
throw new Exception\ApiException($this->getErrorMessage($result), $statusCode);
|
||||
}
|
||||
|
||||
unset($result['success']);
|
||||
|
||||
if (count($result) == 0)
|
||||
return true;
|
||||
|
||||
return reset($result);
|
||||
}
|
||||
}
|
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
|
42
phpunit.xml.dist
Normal file
42
phpunit.xml.dist
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- 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>
|
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
|
||||
{
|
||||
}
|
20
src/Component/Serializer/Annotation/MaxDepth.php
Normal file
20
src/Component/Serializer/Annotation/MaxDepth.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?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 MaxDepth
|
||||
{
|
||||
/**
|
||||
* @Required
|
||||
* @var int
|
||||
*/
|
||||
public $depth;
|
||||
}
|
21
src/Component/Serializer/Annotation/PostDeserialize.php
Normal file
21
src/Component/Serializer/Annotation/PostDeserialize.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* This annotation can be defined on methods which are called after the
|
||||
* deserialization of the object is complete.
|
||||
*
|
||||
* These methods do not necessarily have to be public.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class PostDeserialize
|
||||
{
|
||||
}
|
15
src/Component/Serializer/Annotation/PostSerialize.php
Normal file
15
src/Component/Serializer/Annotation/PostSerialize.php
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class PostSerialize
|
||||
{
|
||||
}
|
22
src/Component/Serializer/Annotation/PreSerialize.php
Normal file
22
src/Component/Serializer/Annotation/PreSerialize.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
/**
|
||||
* This annotation can be declared on methods which should be called
|
||||
* before the Serialization process.
|
||||
*
|
||||
* These methods do not need to be public, and should do any clean-up, or
|
||||
* preparation of the object that is necessary.
|
||||
*
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class PreSerialize
|
||||
{
|
||||
}
|
35
src/Component/Serializer/Annotation/SerializedName.php
Normal file
35
src/Component/Serializer/Annotation/SerializedName.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RetailCrm\Api\Component\Serializer\Annotation;
|
||||
|
||||
use RetailCrm\Api\Component\Serializer\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
* @Target({"PROPERTY","METHOD", "ANNOTATION"})
|
||||
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
|
||||
* @internal
|
||||
*/
|
||||
final class SerializedName
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* SerializedName constructor.
|
||||
*
|
||||
* @param array<string, string> $values
|
||||
*/
|
||||
public function __construct(array $values)
|
||||
{
|
||||
if (!isset($values['value']) || !\is_string($values['value'])) {
|
||||
throw new RuntimeException(sprintf('"value" must be a string.'));
|
||||
}
|
||||
|
||||
$this->name = $values['value'];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue