RequestFactory, easy accessor for FileItemFactory, architectural changes, streams for PSR requests
This commit is contained in:
parent
a40da681ae
commit
eeba164aba
27
src/Component/Exception/FactoryException.php
Normal file
27
src/Component/Exception/FactoryException.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category FactoryException
|
||||||
|
* @package RetailCrm\Component\Exception
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FactoryException
|
||||||
|
*
|
||||||
|
* @category FactoryException
|
||||||
|
* @package RetailCrm\Component\Exception
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class FactoryException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
309
src/Component/Psr7/AppendStream.php
Normal file
309
src/Component/Psr7/AppendStream.php
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category AppendStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AppendStream
|
||||||
|
*
|
||||||
|
* @category AppendStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class AppendStream implements StreamInterface
|
||||||
|
{
|
||||||
|
/** @var StreamInterface[] Streams being decorated */
|
||||||
|
private $streams = [];
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $seekable = true;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $current = 0;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $pos = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppendStream constructor.
|
||||||
|
*
|
||||||
|
* @param StreamInterface[] $streams Streams to decorate. Each stream must
|
||||||
|
* be readable.
|
||||||
|
*/
|
||||||
|
public function __construct(array $streams = [])
|
||||||
|
{
|
||||||
|
foreach ($streams as $stream) {
|
||||||
|
$this->addStream($stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->rewind();
|
||||||
|
|
||||||
|
return $this->getContents();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if (\PHP_VERSION_ID >= 70400) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a stream to the AppendStream
|
||||||
|
*
|
||||||
|
* @param StreamInterface $stream Stream to append. Must be readable.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException if the stream is not readable
|
||||||
|
*/
|
||||||
|
public function addStream(StreamInterface $stream): void
|
||||||
|
{
|
||||||
|
if (!$stream->isReadable()) {
|
||||||
|
throw new \InvalidArgumentException('Each stream must be readable');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The stream is only seekable if all streams are seekable
|
||||||
|
if (!$stream->isSeekable()) {
|
||||||
|
$this->seekable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->streams[] = $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
return Utils::copyToString($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes each attached stream.
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
$this->pos = 0;
|
||||||
|
$this->current = 0;
|
||||||
|
$this->seekable = true;
|
||||||
|
|
||||||
|
foreach ($this->streams as $stream) {
|
||||||
|
$stream->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->streams = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches each attached stream.
|
||||||
|
*
|
||||||
|
* Returns null as it's not clear which underlying stream resource to return.
|
||||||
|
*/
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
$this->pos = $this->current = 0;
|
||||||
|
$this->seekable = true;
|
||||||
|
|
||||||
|
foreach ($this->streams as $stream) {
|
||||||
|
$stream->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->streams = [];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
return $this->pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to calculate the size by adding the size of each stream.
|
||||||
|
*
|
||||||
|
* If any of the streams do not return a valid number, then the size of the
|
||||||
|
* append stream cannot be determined and null is returned.
|
||||||
|
*/
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
$size = 0;
|
||||||
|
|
||||||
|
foreach ($this->streams as $stream) {
|
||||||
|
$s = $stream->getSize();
|
||||||
|
|
||||||
|
if ($s === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$size += $s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return !$this->streams ||
|
||||||
|
($this->current >= count($this->streams) - 1 &&
|
||||||
|
$this->streams[$this->current]->eof());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to seek to the given position. Only supports SEEK_SET.
|
||||||
|
*
|
||||||
|
* @param int $offset
|
||||||
|
* @param int $whence
|
||||||
|
*/
|
||||||
|
public function seek($offset, $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
if (!$this->seekable) {
|
||||||
|
throw new \RuntimeException('This AppendStream is not seekable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($whence !== SEEK_SET) {
|
||||||
|
throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pos = $this->current = 0;
|
||||||
|
|
||||||
|
// Rewind each stream
|
||||||
|
foreach ($this->streams as $i => $stream) {
|
||||||
|
try {
|
||||||
|
$stream->rewind();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \RuntimeException('Unable to seek stream '
|
||||||
|
. $i . ' of the AppendStream', 0, $e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to the actual position by reading from each stream
|
||||||
|
while ($this->pos < $offset && !$this->eof()) {
|
||||||
|
$result = $this->read(min(8096, $offset - $this->pos));
|
||||||
|
if ($result === '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads from all of the appended streams until the length is met or EOF.
|
||||||
|
*
|
||||||
|
* @param int $length
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
$buffer = '';
|
||||||
|
$total = count($this->streams) - 1;
|
||||||
|
$remaining = $length;
|
||||||
|
$progressToNext = false;
|
||||||
|
|
||||||
|
while ($remaining > 0) {
|
||||||
|
if ($progressToNext || $this->streams[$this->current]->eof()) {
|
||||||
|
$progressToNext = false;
|
||||||
|
|
||||||
|
if ($this->current === $total) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->streams[$this->current]->read($remaining);
|
||||||
|
|
||||||
|
if ($result === '') {
|
||||||
|
$progressToNext = true;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $result;
|
||||||
|
$remaining = $length - strlen($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->pos += strlen($buffer);
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return $this->seekable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $string
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Cannot write to an AppendStream');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $key
|
||||||
|
*
|
||||||
|
* @return array|mixed|null
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
return $key ? null : [];
|
||||||
|
}
|
||||||
|
}
|
197
src/Component/Psr7/BufferStream.php
Normal file
197
src/Component/Psr7/BufferStream.php
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category BufferStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BufferStream
|
||||||
|
*
|
||||||
|
* @category BufferStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class BufferStream implements StreamInterface
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
private $hwm;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $buffer = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $hwm High water mark, representing the preferred maximum
|
||||||
|
* buffer size. If the size of the buffer exceeds the high
|
||||||
|
* water mark, then calls to write will continue to succeed
|
||||||
|
* but will return 0 to inform writers to slow down
|
||||||
|
* until the buffer has been drained by reading from it.
|
||||||
|
*/
|
||||||
|
public function __construct(int $hwm = 16384)
|
||||||
|
{
|
||||||
|
$this->hwm = $hwm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->getContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
$buffer = $this->buffer;
|
||||||
|
$this->buffer = '';
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
$this->buffer = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return |null
|
||||||
|
*/
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
$this->close();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
return strlen($this->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $offset
|
||||||
|
* @param int $whence
|
||||||
|
*/
|
||||||
|
public function seek($offset, $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Cannot seek a BufferStream');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return strlen($this->buffer) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Cannot determine the position of a BufferStream');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data from the buffer.
|
||||||
|
*/
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
$currentLength = strlen($this->buffer);
|
||||||
|
|
||||||
|
if ($length >= $currentLength) {
|
||||||
|
// No need to slice the buffer because we don't have enough data.
|
||||||
|
$result = $this->buffer;
|
||||||
|
$this->buffer = '';
|
||||||
|
} else {
|
||||||
|
// Slice up the result to provide a subset of the buffer.
|
||||||
|
$result = substr($this->buffer, 0, $length);
|
||||||
|
$this->buffer = substr($this->buffer, $length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to the buffer.
|
||||||
|
*/
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
$this->buffer .= $string;
|
||||||
|
|
||||||
|
if (strlen($this->buffer) >= $this->hwm) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $key
|
||||||
|
*
|
||||||
|
* @return array|int|null
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
if ($key === 'hwm') {
|
||||||
|
return $this->hwm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key ? null : [];
|
||||||
|
}
|
||||||
|
}
|
386
src/Component/Psr7/MultipartStream.php
Normal file
386
src/Component/Psr7/MultipartStream.php
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category MultipartStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MultipartStream
|
||||||
|
*
|
||||||
|
* @category MultipartStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class MultipartStream implements StreamInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Psr\Http\Message\StreamInterface $stream
|
||||||
|
*/
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $boundary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $elements Array of associative arrays, each containing a
|
||||||
|
* required "name" key mapping to the form field,
|
||||||
|
* name, a required "contents" key mapping to a
|
||||||
|
* StreamInterface/resource/string, an optional
|
||||||
|
* "headers" associative array of custom headers,
|
||||||
|
* and an optional "filename" key mapping to a
|
||||||
|
* string to send as the filename in the part.
|
||||||
|
* @param string|null $boundary You can optionally provide a specific boundary
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct(array $elements = [], string $boundary = null)
|
||||||
|
{
|
||||||
|
$this->boundary = $boundary ?: sha1(uniqid('', true));
|
||||||
|
$this->stream = $this->createStream($elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method used to create a new stream if streams are not added in
|
||||||
|
* the constructor of a decorator.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return StreamInterface
|
||||||
|
*/
|
||||||
|
public function __get(string $name)
|
||||||
|
{
|
||||||
|
if ($name === 'stream') {
|
||||||
|
$this->stream = $this->createStream();
|
||||||
|
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \UnexpectedValueException("$name not found on class");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @param $value
|
||||||
|
*/
|
||||||
|
public function __set(string $name, $value)
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
*/
|
||||||
|
public function __isset(string $name)
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isSeekable()) {
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getContents();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if (\PHP_VERSION_ID >= 70400) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow decorators to implement custom methods
|
||||||
|
*
|
||||||
|
* @param string $method
|
||||||
|
* @param array $args
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __call(string $method, array $args)
|
||||||
|
{
|
||||||
|
/** @var callable $callable */
|
||||||
|
$callable = [$this->stream, $method];
|
||||||
|
$result = call_user_func_array($callable, $args);
|
||||||
|
|
||||||
|
return $result === $this->stream ? $this : $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
return Utils::copyToString($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* close
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
$this->stream->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $key
|
||||||
|
*
|
||||||
|
* @return array|mixed|null
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
return $this->stream->getMetadata($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null
|
||||||
|
*/
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
return $this->stream->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->stream->getSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
return $this->stream->tell();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isReadable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isWritable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return $this->stream->isSeekable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rewind
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $offset
|
||||||
|
* @param int $whence
|
||||||
|
*/
|
||||||
|
public function seek($offset, $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
$this->stream->seek($offset, $whence);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $length
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
return $this->stream->read($length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $string
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
return $this->stream->write($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBoundary(): string
|
||||||
|
{
|
||||||
|
return $this->boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the aggregate stream that will be used to upload the POST data
|
||||||
|
*
|
||||||
|
* @param array $elements
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\StreamInterface
|
||||||
|
*/
|
||||||
|
protected function createStream(array $elements = []): StreamInterface
|
||||||
|
{
|
||||||
|
$stream = new AppendStream();
|
||||||
|
|
||||||
|
foreach ($elements as $element) {
|
||||||
|
$this->addElement($stream, $element);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the trailing boundary with CRLF
|
||||||
|
$stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
|
||||||
|
|
||||||
|
return $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the headers needed before transferring the content of a POST file
|
||||||
|
*
|
||||||
|
* @param array<string, string> $headers
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getHeaders(array $headers): string
|
||||||
|
{
|
||||||
|
$str = '';
|
||||||
|
|
||||||
|
foreach ($headers as $key => $value) {
|
||||||
|
$str .= "{$key}: {$value}\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RetailCrm\Component\Psr7\AppendStream $stream
|
||||||
|
* @param array $element
|
||||||
|
*/
|
||||||
|
private function addElement(AppendStream $stream, array $element): void
|
||||||
|
{
|
||||||
|
foreach (['contents', 'name'] as $key) {
|
||||||
|
if (!array_key_exists($key, $element)) {
|
||||||
|
throw new \InvalidArgumentException("A '{$key}' key is required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$element['contents'] = Utils::streamFor($element['contents']);
|
||||||
|
|
||||||
|
if (empty($element['filename'])) {
|
||||||
|
$uri = $element['contents']->getMetadata('uri');
|
||||||
|
|
||||||
|
if (substr($uri, 0, 6) !== 'php://') {
|
||||||
|
$element['filename'] = $uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[$body, $headers] = $this->createElement(
|
||||||
|
$element['name'],
|
||||||
|
$element['contents'],
|
||||||
|
$element['filename'] ?? null,
|
||||||
|
$element['headers'] ?? []
|
||||||
|
);
|
||||||
|
|
||||||
|
$stream->addStream(Utils::streamFor($this->getHeaders($headers)));
|
||||||
|
$stream->addStream($body);
|
||||||
|
$stream->addStream(Utils::streamFor("\r\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $name
|
||||||
|
* @param \Psr\Http\Message\StreamInterface $stream
|
||||||
|
* @param string|null $filename
|
||||||
|
* @param array $headers
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
|
||||||
|
{
|
||||||
|
// Set a default content-disposition header if one was no provided
|
||||||
|
$disposition = $this->getHeader($headers, 'content-disposition');
|
||||||
|
|
||||||
|
if (!$disposition) {
|
||||||
|
$headers['Content-Disposition'] = ($filename === '0' || $filename)
|
||||||
|
? sprintf(
|
||||||
|
'form-data; name="%s"; filename="%s"',
|
||||||
|
$name,
|
||||||
|
basename($filename)
|
||||||
|
)
|
||||||
|
: "form-data; name=\"{$name}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a default content-length header if one was no provided
|
||||||
|
$length = $this->getHeader($headers, 'content-length');
|
||||||
|
|
||||||
|
if (!$length && $length = $stream->getSize()) {
|
||||||
|
$headers['Content-Length'] = (string) $length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a default Content-Type if one was not supplied
|
||||||
|
$type = $this->getHeader($headers, 'content-type');
|
||||||
|
|
||||||
|
if (!$type && ($filename === '0' || $filename) && $type = Utils::mimetypeFromFilename($filename)) {
|
||||||
|
$headers['Content-Type'] = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$stream, $headers];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $headers
|
||||||
|
* @param string $key
|
||||||
|
*
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
private function getHeader(array $headers, string $key)
|
||||||
|
{
|
||||||
|
$lowercaseHeader = strtolower($key);
|
||||||
|
foreach ($headers as $k => $v) {
|
||||||
|
if (strtolower($k) === $lowercaseHeader) {
|
||||||
|
return $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
241
src/Component/Psr7/PumpStream.php
Normal file
241
src/Component/Psr7/PumpStream.php
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category PumpStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class PumpStream
|
||||||
|
*
|
||||||
|
* @category PumpStream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class PumpStream implements StreamInterface
|
||||||
|
{
|
||||||
|
/** @var callable|null */
|
||||||
|
private $source;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $size;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $tellPos = 0;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $metadata;
|
||||||
|
|
||||||
|
/** @var BufferStream */
|
||||||
|
private $buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable(int): (string|null|false) $source Source of the stream data. The callable MAY
|
||||||
|
* accept an integer argument used to control the
|
||||||
|
* amount of data to return. The callable MUST
|
||||||
|
* return a string when called, or false|null on error
|
||||||
|
* or EOF.
|
||||||
|
* @param array{size?: int, metadata?: array} $options Stream options:
|
||||||
|
* - metadata: Hash of metadata to use with stream.
|
||||||
|
* - size: Size of the stream, if known.
|
||||||
|
*/
|
||||||
|
public function __construct(callable $source, array $options = [])
|
||||||
|
{
|
||||||
|
$this->source = $source;
|
||||||
|
$this->size = $options['size'] ?? null;
|
||||||
|
$this->metadata = $options['metadata'] ?? [];
|
||||||
|
$this->buffer = new BufferStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return Utils::copyToString($this);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if (\PHP_VERSION_ID >= 70400) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
$this->detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return |null
|
||||||
|
*/
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
$this->tellPos = 0;
|
||||||
|
$this->source = null;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
return $this->tellPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
return $this->source === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $offset
|
||||||
|
* @param int $whence
|
||||||
|
*/
|
||||||
|
public function seek($offset, $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Cannot seek a PumpStream');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $string
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
throw new \RuntimeException('Cannot write to a PumpStream');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $length
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
$data = $this->buffer->read($length);
|
||||||
|
$readLen = strlen($data);
|
||||||
|
$this->tellPos += $readLen;
|
||||||
|
$remaining = $length - $readLen;
|
||||||
|
|
||||||
|
if ($remaining) {
|
||||||
|
$this->pump($remaining);
|
||||||
|
$data .= $this->buffer->read($remaining);
|
||||||
|
$this->tellPos += strlen($data) - $readLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
$result = '';
|
||||||
|
while (!$this->eof()) {
|
||||||
|
$result .= $this->read(1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $key
|
||||||
|
*
|
||||||
|
* @return array|mixed|null
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
if (!$key) {
|
||||||
|
return $this->metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->metadata[$key] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $length
|
||||||
|
*/
|
||||||
|
private function pump(int $length): void
|
||||||
|
{
|
||||||
|
if ($this->source) {
|
||||||
|
do {
|
||||||
|
$data = call_user_func($this->source, $length);
|
||||||
|
|
||||||
|
if ($data === false || $data === null) {
|
||||||
|
$this->source = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->buffer->write($data);
|
||||||
|
$length -= strlen($data);
|
||||||
|
} while ($length > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
362
src/Component/Psr7/Stream.php
Normal file
362
src/Component/Psr7/Stream.php
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category Stream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Stream
|
||||||
|
*
|
||||||
|
* @category Stream
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class Stream implements StreamInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @see http://php.net/manual/function.fopen.php
|
||||||
|
* @see http://php.net/manual/en/function.gzopen.php
|
||||||
|
*/
|
||||||
|
private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
private $size;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $seekable;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $readable;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $writable;
|
||||||
|
|
||||||
|
/** @var string|null */
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
/** @var mixed[] */
|
||||||
|
private $customMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This constructor accepts an associative array of options.
|
||||||
|
*
|
||||||
|
* - size: (int) If a read stream would otherwise have an indeterminate
|
||||||
|
* size, but the size is known due to foreknowledge, then you can
|
||||||
|
* provide that size, in bytes.
|
||||||
|
* - metadata: (array) Any additional metadata to return when the metadata
|
||||||
|
* of the stream is accessed.
|
||||||
|
*
|
||||||
|
* @param resource $stream Stream resource to wrap.
|
||||||
|
* @param array{size?: int, metadata?: array} $options Associative array of options.
|
||||||
|
*
|
||||||
|
* @throws \InvalidArgumentException if the stream is not a stream resource
|
||||||
|
*/
|
||||||
|
public function __construct($stream, array $options = [])
|
||||||
|
{
|
||||||
|
if (!is_resource($stream)) {
|
||||||
|
throw new \InvalidArgumentException('Stream must be a resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($options['size'])) {
|
||||||
|
$this->size = $options['size'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->customMetadata = $options['metadata'] ?? [];
|
||||||
|
$this->stream = $stream;
|
||||||
|
$meta = stream_get_meta_data($this->stream);
|
||||||
|
$this->seekable = $meta['seekable'];
|
||||||
|
$this->readable = (bool)preg_match(self::READABLE_MODES, $meta['mode']);
|
||||||
|
$this->writable = (bool)preg_match(self::WRITABLE_MODES, $meta['mode']);
|
||||||
|
$this->uri = $this->getMetadata('uri');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the stream when the destructed
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
* @throws \Throwable
|
||||||
|
*/
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->isSeekable()) {
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->getContents();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
if (\PHP_VERSION_ID >= 70400) {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = stream_get_contents($this->stream);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
throw new \RuntimeException('Unable to read stream contents');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function close(): void
|
||||||
|
{
|
||||||
|
if (isset($this->stream)) {
|
||||||
|
if (is_resource($this->stream)) {
|
||||||
|
fclose($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null
|
||||||
|
*/
|
||||||
|
public function detach()
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->stream;
|
||||||
|
unset($this->stream);
|
||||||
|
$this->size = $this->uri = null;
|
||||||
|
$this->readable = false;
|
||||||
|
$this->writable = false;
|
||||||
|
$this->seekable = false;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int|null
|
||||||
|
*/
|
||||||
|
public function getSize(): ?int
|
||||||
|
{
|
||||||
|
if ($this->size !== null) {
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the stat cache if the stream has a URI
|
||||||
|
if ($this->uri) {
|
||||||
|
clearstatcache(true, $this->uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats = fstat($this->stream);
|
||||||
|
if (is_array($stats) && isset($stats['size'])) {
|
||||||
|
$this->size = $stats['size'];
|
||||||
|
return $this->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isReadable(): bool
|
||||||
|
{
|
||||||
|
return $this->readable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isWritable(): bool
|
||||||
|
{
|
||||||
|
return $this->writable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isSeekable(): bool
|
||||||
|
{
|
||||||
|
return $this->seekable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function eof(): bool
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
return feof($this->stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function tell(): int
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = ftell($this->stream);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
throw new \RuntimeException('Unable to determine stream position');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function rewind(): void
|
||||||
|
{
|
||||||
|
$this->seek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $offset
|
||||||
|
* @param int $whence
|
||||||
|
*/
|
||||||
|
public function seek($offset, $whence = SEEK_SET): void
|
||||||
|
{
|
||||||
|
$whence = (int) $whence;
|
||||||
|
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->seekable) {
|
||||||
|
throw new \RuntimeException('Stream is not seekable');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fseek($this->stream, $offset, $whence) === -1) {
|
||||||
|
throw new \RuntimeException('Unable to seek to stream position '
|
||||||
|
. $offset . ' with whence ' . var_export($whence, true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $length
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($length): string
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->readable) {
|
||||||
|
throw new \RuntimeException('Cannot read from non-readable stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($length < 0) {
|
||||||
|
throw new \RuntimeException('Length parameter cannot be negative');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 === $length) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$string = fread($this->stream, $length);
|
||||||
|
|
||||||
|
if (false === $string) {
|
||||||
|
throw new \RuntimeException('Unable to read from stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $string
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function write($string): int
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
throw new \RuntimeException('Stream is detached');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->writable) {
|
||||||
|
throw new \RuntimeException('Cannot write to a non-writable stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't know the size after writing anything
|
||||||
|
$this->size = null;
|
||||||
|
$result = fwrite($this->stream, $string);
|
||||||
|
|
||||||
|
if ($result === false) {
|
||||||
|
throw new \RuntimeException('Unable to write to stream');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $key
|
||||||
|
*
|
||||||
|
* @return array|mixed|mixed[]|null
|
||||||
|
*/
|
||||||
|
public function getMetadata($key = null)
|
||||||
|
{
|
||||||
|
if (!isset($this->stream)) {
|
||||||
|
return $key ? null : [];
|
||||||
|
} elseif (!$key) {
|
||||||
|
return $this->customMetadata + stream_get_meta_data($this->stream);
|
||||||
|
} elseif (isset($this->customMetadata[$key])) {
|
||||||
|
return $this->customMetadata[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = stream_get_meta_data($this->stream);
|
||||||
|
|
||||||
|
return $meta[$key] ?? null;
|
||||||
|
}
|
||||||
|
}
|
304
src/Component/Psr7/Utils.php
Normal file
304
src/Component/Psr7/Utils.php
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category Utils
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component\Psr7;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Utils
|
||||||
|
*
|
||||||
|
* @category Utils
|
||||||
|
* @package RetailCrm\Component\Psr7
|
||||||
|
* @author Michael Dowling <mtdowling@gmail.com>
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class Utils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Copy the contents of a stream into a string until the given number of
|
||||||
|
* bytes have been read.
|
||||||
|
*
|
||||||
|
* @param StreamInterface $stream Stream to read
|
||||||
|
* @param int $maxLen Maximum number of bytes to read. Pass -1
|
||||||
|
* to read the entire stream.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws \RuntimeException on error.
|
||||||
|
*/
|
||||||
|
public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
|
||||||
|
{
|
||||||
|
$buffer = '';
|
||||||
|
|
||||||
|
if ($maxLen === -1) {
|
||||||
|
while (!$stream->eof()) {
|
||||||
|
$buf = $stream->read(1048576);
|
||||||
|
|
||||||
|
if ($buf === '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer .= $buf;
|
||||||
|
}
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
$len = 0;
|
||||||
|
|
||||||
|
while (!$stream->eof() && $len < $maxLen) {
|
||||||
|
$buf = $stream->read($maxLen - $len);
|
||||||
|
if ($buf === '') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$buffer .= $buf;
|
||||||
|
$len = strlen($buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely opens a PHP stream resource using a filename.
|
||||||
|
*
|
||||||
|
* When fopen fails, PHP normally raises a warning. This function adds an
|
||||||
|
* error handler that checks for errors and throws an exception instead.
|
||||||
|
*
|
||||||
|
* @param string $filename File to open
|
||||||
|
* @param string $mode Mode used to open the file
|
||||||
|
*
|
||||||
|
* @return resource
|
||||||
|
*
|
||||||
|
* @throws \RuntimeException if the file cannot be opened
|
||||||
|
*/
|
||||||
|
public static function tryFopen(string $filename, string $mode)
|
||||||
|
{
|
||||||
|
$ex = null;
|
||||||
|
|
||||||
|
set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
|
||||||
|
$ex = new \RuntimeException(sprintf(
|
||||||
|
'Unable to open %s using mode %s: %s',
|
||||||
|
$filename,
|
||||||
|
$mode,
|
||||||
|
$errstr
|
||||||
|
));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @var resource $handle */
|
||||||
|
$handle = fopen($filename, $mode);
|
||||||
|
|
||||||
|
restore_error_handler();
|
||||||
|
|
||||||
|
if ($ex) {
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stream based on the input type.
|
||||||
|
*
|
||||||
|
* Options is an associative array that can contain the following keys:
|
||||||
|
* - metadata: Array of custom metadata.
|
||||||
|
* - size: Size of the stream.
|
||||||
|
*
|
||||||
|
* @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
|
||||||
|
* @param array{size?: int, metadata?: array} $options Additional options
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\StreamInterface
|
||||||
|
* @throws \InvalidArgumentException if the $resource arg is not valid.
|
||||||
|
*/
|
||||||
|
public static function streamFor($resource = '', array $options = []): StreamInterface
|
||||||
|
{
|
||||||
|
if (is_scalar($resource)) {
|
||||||
|
$stream = self::tryFopen('php://temp', 'r+');
|
||||||
|
|
||||||
|
if ($resource !== '') {
|
||||||
|
fwrite($stream, (string) $resource);
|
||||||
|
fseek($stream, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Stream($stream, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (gettype($resource)) {
|
||||||
|
case 'resource':
|
||||||
|
/** @var resource $resource */
|
||||||
|
return new Stream($resource, $options);
|
||||||
|
case 'object':
|
||||||
|
/** @var object $resource */
|
||||||
|
if ($resource instanceof StreamInterface) {
|
||||||
|
return $resource;
|
||||||
|
} elseif ($resource instanceof \Iterator) {
|
||||||
|
return new PumpStream(function () use ($resource) {
|
||||||
|
if (!$resource->valid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $resource->current();
|
||||||
|
$resource->next();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}, $options);
|
||||||
|
} elseif (method_exists($resource, '__toString')) {
|
||||||
|
return self::streamFor((string) $resource, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'NULL':
|
||||||
|
return new Stream(self::tryFopen('php://temp', 'r+'), $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_callable($resource)) {
|
||||||
|
return new PumpStream($resource, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException('Invalid resource type: ' . gettype($resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the mimetype of a file by looking at its extension.
|
||||||
|
*
|
||||||
|
* @param string $filename
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function mimetypeFromFilename(string $filename): ?string
|
||||||
|
{
|
||||||
|
return self::mimetypeFromExtension(pathinfo($filename, PATHINFO_EXTENSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a file extensions to a mimetype.
|
||||||
|
*
|
||||||
|
* @link http://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x/conf/mime.types
|
||||||
|
*
|
||||||
|
* @param string $extension
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function mimetypeFromExtension(string $extension): ?string
|
||||||
|
{
|
||||||
|
static $mimetypes = [
|
||||||
|
'3gp' => 'video/3gpp',
|
||||||
|
'7z' => 'application/x-7z-compressed',
|
||||||
|
'aac' => 'audio/x-aac',
|
||||||
|
'ai' => 'application/postscript',
|
||||||
|
'aif' => 'audio/x-aiff',
|
||||||
|
'asc' => 'text/plain',
|
||||||
|
'asf' => 'video/x-ms-asf',
|
||||||
|
'atom' => 'application/atom+xml',
|
||||||
|
'avi' => 'video/x-msvideo',
|
||||||
|
'bmp' => 'image/bmp',
|
||||||
|
'bz2' => 'application/x-bzip2',
|
||||||
|
'cer' => 'application/pkix-cert',
|
||||||
|
'crl' => 'application/pkix-crl',
|
||||||
|
'crt' => 'application/x-x509-ca-cert',
|
||||||
|
'css' => 'text/css',
|
||||||
|
'csv' => 'text/csv',
|
||||||
|
'cu' => 'application/cu-seeme',
|
||||||
|
'deb' => 'application/x-debian-package',
|
||||||
|
'doc' => 'application/msword',
|
||||||
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
|
'dvi' => 'application/x-dvi',
|
||||||
|
'eot' => 'application/vnd.ms-fontobject',
|
||||||
|
'eps' => 'application/postscript',
|
||||||
|
'epub' => 'application/epub+zip',
|
||||||
|
'etx' => 'text/x-setext',
|
||||||
|
'flac' => 'audio/flac',
|
||||||
|
'flv' => 'video/x-flv',
|
||||||
|
'gif' => 'image/gif',
|
||||||
|
'gz' => 'application/gzip',
|
||||||
|
'htm' => 'text/html',
|
||||||
|
'html' => 'text/html',
|
||||||
|
'ico' => 'image/x-icon',
|
||||||
|
'ics' => 'text/calendar',
|
||||||
|
'ini' => 'text/plain',
|
||||||
|
'iso' => 'application/x-iso9660-image',
|
||||||
|
'jar' => 'application/java-archive',
|
||||||
|
'jpe' => 'image/jpeg',
|
||||||
|
'jpeg' => 'image/jpeg',
|
||||||
|
'jpg' => 'image/jpeg',
|
||||||
|
'js' => 'text/javascript',
|
||||||
|
'json' => 'application/json',
|
||||||
|
'latex' => 'application/x-latex',
|
||||||
|
'log' => 'text/plain',
|
||||||
|
'm4a' => 'audio/mp4',
|
||||||
|
'm4v' => 'video/mp4',
|
||||||
|
'mid' => 'audio/midi',
|
||||||
|
'midi' => 'audio/midi',
|
||||||
|
'mov' => 'video/quicktime',
|
||||||
|
'mkv' => 'video/x-matroska',
|
||||||
|
'mp3' => 'audio/mpeg',
|
||||||
|
'mp4' => 'video/mp4',
|
||||||
|
'mp4a' => 'audio/mp4',
|
||||||
|
'mp4v' => 'video/mp4',
|
||||||
|
'mpe' => 'video/mpeg',
|
||||||
|
'mpeg' => 'video/mpeg',
|
||||||
|
'mpg' => 'video/mpeg',
|
||||||
|
'mpg4' => 'video/mp4',
|
||||||
|
'oga' => 'audio/ogg',
|
||||||
|
'ogg' => 'audio/ogg',
|
||||||
|
'ogv' => 'video/ogg',
|
||||||
|
'ogx' => 'application/ogg',
|
||||||
|
'pbm' => 'image/x-portable-bitmap',
|
||||||
|
'pdf' => 'application/pdf',
|
||||||
|
'pgm' => 'image/x-portable-graymap',
|
||||||
|
'png' => 'image/png',
|
||||||
|
'pnm' => 'image/x-portable-anymap',
|
||||||
|
'ppm' => 'image/x-portable-pixmap',
|
||||||
|
'ppt' => 'application/vnd.ms-powerpoint',
|
||||||
|
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||||
|
'ps' => 'application/postscript',
|
||||||
|
'qt' => 'video/quicktime',
|
||||||
|
'rar' => 'application/x-rar-compressed',
|
||||||
|
'ras' => 'image/x-cmu-raster',
|
||||||
|
'rss' => 'application/rss+xml',
|
||||||
|
'rtf' => 'application/rtf',
|
||||||
|
'sgm' => 'text/sgml',
|
||||||
|
'sgml' => 'text/sgml',
|
||||||
|
'svg' => 'image/svg+xml',
|
||||||
|
'swf' => 'application/x-shockwave-flash',
|
||||||
|
'tar' => 'application/x-tar',
|
||||||
|
'tif' => 'image/tiff',
|
||||||
|
'tiff' => 'image/tiff',
|
||||||
|
'torrent' => 'application/x-bittorrent',
|
||||||
|
'ttf' => 'application/x-font-ttf',
|
||||||
|
'txt' => 'text/plain',
|
||||||
|
'wav' => 'audio/x-wav',
|
||||||
|
'webm' => 'video/webm',
|
||||||
|
'webp' => 'image/webp',
|
||||||
|
'wma' => 'audio/x-ms-wma',
|
||||||
|
'wmv' => 'video/x-ms-wmv',
|
||||||
|
'woff' => 'application/x-font-woff',
|
||||||
|
'wsdl' => 'application/wsdl+xml',
|
||||||
|
'xbm' => 'image/x-xbitmap',
|
||||||
|
'xls' => 'application/vnd.ms-excel',
|
||||||
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
|
'xml' => 'application/xml',
|
||||||
|
'xpm' => 'image/x-xpixmap',
|
||||||
|
'xwd' => 'image/x-xwindowdump',
|
||||||
|
'yaml' => 'text/yaml',
|
||||||
|
'yml' => 'text/yaml',
|
||||||
|
'zip' => 'application/zip',
|
||||||
|
];
|
||||||
|
|
||||||
|
$extension = strtolower($extension);
|
||||||
|
|
||||||
|
return $mimetypes[$extension] ?? null;
|
||||||
|
}
|
||||||
|
}
|
41
src/Component/ServiceLocator.php
Normal file
41
src/Component/ServiceLocator.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category ServiceLocator
|
||||||
|
* @package RetailCrm\Component
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Component;
|
||||||
|
|
||||||
|
use RetailCrm\Factory\FileItemFactory;
|
||||||
|
use RetailCrm\Interfaces\ContainerAwareInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemFactoryInterface;
|
||||||
|
use RetailCrm\Traits\ContainerAwareTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ServiceLocator
|
||||||
|
*
|
||||||
|
* @category ServiceLocator
|
||||||
|
* @package RetailCrm\Component
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class ServiceLocator implements ContainerAwareInterface
|
||||||
|
{
|
||||||
|
use ContainerAwareTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \RetailCrm\Interfaces\FileItemFactoryInterface
|
||||||
|
*/
|
||||||
|
public function getFileItemFactory(): FileItemFactoryInterface
|
||||||
|
{
|
||||||
|
return $this->getContainer()->get(FileItemFactory::class);
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ namespace RetailCrm\Factory;
|
|||||||
|
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use RetailCrm\Component\Constants;
|
use RetailCrm\Component\Constants;
|
||||||
|
use RetailCrm\Component\ServiceLocator;
|
||||||
use RetailCrm\Interfaces\AppDataInterface;
|
use RetailCrm\Interfaces\AppDataInterface;
|
||||||
use RetailCrm\Interfaces\AuthenticatorInterface;
|
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||||
use RetailCrm\Interfaces\ContainerAwareInterface;
|
use RetailCrm\Interfaces\ContainerAwareInterface;
|
||||||
@ -87,6 +88,7 @@ class ClientFactory implements ContainerAwareInterface, FactoryInterface
|
|||||||
$client->setSerializer($this->container->get(Constants::SERIALIZER));
|
$client->setSerializer($this->container->get(Constants::SERIALIZER));
|
||||||
$client->setValidator($this->container->get(Constants::VALIDATOR));
|
$client->setValidator($this->container->get(Constants::VALIDATOR));
|
||||||
$client->setRequestFactory($this->container->get(RequestFactory::class));
|
$client->setRequestFactory($this->container->get(RequestFactory::class));
|
||||||
|
$client->setServiceLocator($this->container->get(ServiceLocator::class));
|
||||||
$client->validateSelf();
|
$client->validateSelf();
|
||||||
|
|
||||||
return $client;
|
return $client;
|
||||||
|
@ -13,22 +13,23 @@
|
|||||||
namespace RetailCrm\Factory;
|
namespace RetailCrm\Factory;
|
||||||
|
|
||||||
use JMS\Serializer\GraphNavigatorInterface;
|
use JMS\Serializer\GraphNavigatorInterface;
|
||||||
|
use JMS\Serializer\Handler\HandlerRegistry;
|
||||||
use JMS\Serializer\Serializer;
|
use JMS\Serializer\Serializer;
|
||||||
use RetailCrm\Component\Constants;
|
|
||||||
use RetailCrm\Service\RequestSigner;
|
|
||||||
use Shieldon\Psr17\StreamFactory;
|
|
||||||
use Shieldon\Psr17\UploadedFileFactory as BaseUploadedFileFactory;
|
|
||||||
use Shieldon\Psr17\RequestFactory as BaseRequestFactory;
|
|
||||||
use JMS\Serializer\SerializerBuilder;
|
use JMS\Serializer\SerializerBuilder;
|
||||||
|
use JMS\Serializer\SerializerInterface;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Http\Client\ClientInterface;
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use RetailCrm\Component\Constants;
|
||||||
use RetailCrm\Component\DependencyInjection\Container;
|
use RetailCrm\Component\DependencyInjection\Container;
|
||||||
use RetailCrm\Component\Environment;
|
use RetailCrm\Component\Environment;
|
||||||
|
use RetailCrm\Component\ServiceLocator;
|
||||||
use RetailCrm\Interfaces\FactoryInterface;
|
use RetailCrm\Interfaces\FactoryInterface;
|
||||||
|
use RetailCrm\Service\RequestDataFilter;
|
||||||
|
use RetailCrm\Service\RequestSigner;
|
||||||
|
use Shieldon\Psr17\StreamFactory;
|
||||||
use Symfony\Component\Validator\Validation;
|
use Symfony\Component\Validator\Validation;
|
||||||
use Symfony\Component\Validator\Validator\TraceableValidator;
|
use Symfony\Component\Validator\Validator\TraceableValidator;
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
use JMS\Serializer\Handler\HandlerRegistry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class EnvironmentFactory
|
* Class EnvironmentFactory
|
||||||
@ -109,17 +110,29 @@ class ContainerFactory implements FactoryInterface
|
|||||||
Constants::VALIDATOR,
|
Constants::VALIDATOR,
|
||||||
Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator()
|
Validation::createValidatorBuilder()->enableAnnotationMapping()->getValidator()
|
||||||
);
|
);
|
||||||
$container->set(Constants::SERIALIZER, $this->getSerializer());
|
$container->set(Constants::SERIALIZER, function (ContainerInterface $container) {
|
||||||
$container->set(UploadedFileFactory::class, new UploadedFileFactory(
|
return SerializerFactory::withContainer($container)->create();
|
||||||
new BaseUploadedFileFactory(),
|
});
|
||||||
new StreamFactory())
|
$container->set(FileItemFactory::class, new FileItemFactory(new StreamFactory()));
|
||||||
);
|
$container->set(RequestDataFilter::class, new RequestDataFilter());
|
||||||
$container->set(BaseRequestFactory::class, new BaseRequestFactory());
|
|
||||||
$container->set(RequestSigner::class, function (ContainerInterface $container) {
|
$container->set(RequestSigner::class, function (ContainerInterface $container) {
|
||||||
return new RequestSigner($container->get(Constants::SERIALIZER));
|
return new RequestSigner(
|
||||||
|
$container->get(Constants::SERIALIZER),
|
||||||
|
$container->get(RequestDataFilter::class)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
$container->set(RequestFactory::class, function (ContainerInterface $container) {
|
$container->set(RequestFactory::class, function (ContainerInterface $container) {
|
||||||
return new RequestFactory($container->get(RequestSigner::class), $container->get(BaseRequestFactory::class));
|
return new RequestFactory(
|
||||||
|
$container->get(RequestSigner::class),
|
||||||
|
$container->get(RequestDataFilter::class),
|
||||||
|
$container->get(Constants::SERIALIZER)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
$container->set(ServiceLocator::class, function (ContainerInterface $container) {
|
||||||
|
$locator = new ServiceLocator();
|
||||||
|
$locator->setContainer($container);
|
||||||
|
|
||||||
|
return $locator;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,43 +147,4 @@ class ContainerFactory implements FactoryInterface
|
|||||||
$container->set('validator', new TraceableValidator($validator));
|
$container->set('validator', new TraceableValidator($validator));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return \JMS\Serializer\Serializer
|
|
||||||
*/
|
|
||||||
protected function getSerializer(): Serializer
|
|
||||||
{
|
|
||||||
return SerializerBuilder::create()
|
|
||||||
->configureHandlers(function(HandlerRegistry $registry) {
|
|
||||||
$returnNull = function($visitor, $obj, array $type) {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
$registry->registerHandler(
|
|
||||||
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
|
||||||
'UploadedFileInterface',
|
|
||||||
'json',
|
|
||||||
$returnNull
|
|
||||||
);
|
|
||||||
$registry->registerHandler(
|
|
||||||
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
|
||||||
'UploadedFileInterface',
|
|
||||||
'json',
|
|
||||||
$returnNull
|
|
||||||
);
|
|
||||||
$registry->registerHandler(
|
|
||||||
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
|
||||||
'UploadedFileInterface',
|
|
||||||
'xml',
|
|
||||||
$returnNull
|
|
||||||
);
|
|
||||||
$registry->registerHandler(
|
|
||||||
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
|
||||||
'UploadedFileInterface',
|
|
||||||
'xml',
|
|
||||||
$returnNull
|
|
||||||
);
|
|
||||||
})->addDefaultHandlers()
|
|
||||||
->build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
65
src/Factory/FileItemFactory.php
Normal file
65
src/Factory/FileItemFactory.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category FileItemFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Factory;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamFactoryInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemFactoryInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemInterface;
|
||||||
|
use RetailCrm\Model\FileItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FileItemFactory
|
||||||
|
*
|
||||||
|
* @category FileItemFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class FileItemFactory implements FileItemFactoryInterface
|
||||||
|
{
|
||||||
|
/** @var StreamFactoryInterface $streamFactory */
|
||||||
|
private $streamFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileItemFactory constructor.
|
||||||
|
*
|
||||||
|
* @param \Psr\Http\Message\StreamFactoryInterface $streamFactory
|
||||||
|
*/
|
||||||
|
public function __construct(StreamFactoryInterface $streamFactory)
|
||||||
|
{
|
||||||
|
$this->streamFactory = $streamFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fileName Name without path
|
||||||
|
* @param string $contents
|
||||||
|
*
|
||||||
|
* @return FileItemInterface
|
||||||
|
*/
|
||||||
|
public function fromString(string $fileName, string $contents): FileItemInterface
|
||||||
|
{
|
||||||
|
return new FileItem($fileName, $this->streamFactory->createStream($contents));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fileName Name with or without path
|
||||||
|
*
|
||||||
|
* @return FileItemInterface
|
||||||
|
*/
|
||||||
|
public function fromFile(string $fileName): FileItemInterface
|
||||||
|
{
|
||||||
|
return new FileItem(basename($fileName), $this->streamFactory->createStreamFromFile($fileName));
|
||||||
|
}
|
||||||
|
}
|
@ -12,11 +12,19 @@
|
|||||||
*/
|
*/
|
||||||
namespace RetailCrm\Factory;
|
namespace RetailCrm\Factory;
|
||||||
|
|
||||||
use Psr\Http\Message\RequestFactoryInterface as BaseFactoryInterface;
|
use JMS\Serializer\SerializerInterface;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use RetailCrm\Component\Exception\FactoryException;
|
||||||
|
use RetailCrm\Component\Psr7\MultipartStream;
|
||||||
use RetailCrm\Interfaces\AppDataInterface;
|
use RetailCrm\Interfaces\AppDataInterface;
|
||||||
|
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemInterface;
|
||||||
use RetailCrm\Interfaces\RequestFactoryInterface;
|
use RetailCrm\Interfaces\RequestFactoryInterface;
|
||||||
use RetailCrm\Interfaces\RequestSignerInterface;
|
use RetailCrm\Interfaces\RequestSignerInterface;
|
||||||
use RetailCrm\Model\Request\BaseRequest;
|
use RetailCrm\Model\Request\BaseRequest;
|
||||||
|
use RetailCrm\Service\RequestDataFilter;
|
||||||
|
use Shieldon\Psr7\Request;
|
||||||
|
use Shieldon\Psr7\Uri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class RequestFactory
|
* Class RequestFactory
|
||||||
@ -36,29 +44,120 @@ class RequestFactory implements RequestFactoryInterface
|
|||||||
private $signer;
|
private $signer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var BaseFactoryInterface $requestFactory
|
* @var RequestDataFilter $filter
|
||||||
*/
|
*/
|
||||||
private $requestFactory;
|
private $filter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SerializerInterface|\JMS\Serializer\Serializer $serializer
|
||||||
|
*/
|
||||||
|
private $serializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RequestFactory constructor.
|
* RequestFactory constructor.
|
||||||
*
|
*
|
||||||
* @param \RetailCrm\Interfaces\RequestSignerInterface $signer
|
* @param \RetailCrm\Interfaces\RequestSignerInterface $signer
|
||||||
* @param \Psr\Http\Message\RequestFactoryInterface $requestFactory
|
* @param \RetailCrm\Service\RequestDataFilter $filter
|
||||||
|
* @param \JMS\Serializer\SerializerInterface $serializer
|
||||||
*/
|
*/
|
||||||
public function __construct(RequestSignerInterface $signer, BaseFactoryInterface $requestFactory)
|
public function __construct(
|
||||||
{
|
RequestSignerInterface $signer,
|
||||||
|
RequestDataFilter $filter,
|
||||||
|
SerializerInterface $serializer
|
||||||
|
) {
|
||||||
$this->signer = $signer;
|
$this->signer = $signer;
|
||||||
$this->requestFactory = $requestFactory;
|
$this->filter = $filter;
|
||||||
|
$this->serializer = $serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \RetailCrm\Model\Request\BaseRequest $request
|
* @param string $endpoint
|
||||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
* @param \RetailCrm\Model\Request\BaseRequest $request
|
||||||
|
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||||
|
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\RequestInterface
|
||||||
|
* @throws \RetailCrm\Component\Exception\FactoryException
|
||||||
*/
|
*/
|
||||||
public function fromModel(BaseRequest $request, AppDataInterface $appData)
|
public function fromModel(
|
||||||
{
|
string $endpoint,
|
||||||
|
BaseRequest $request,
|
||||||
|
AppDataInterface $appData,
|
||||||
|
AuthenticatorInterface $authenticator
|
||||||
|
): RequestInterface {
|
||||||
|
$authenticator->authenticate($request);
|
||||||
$this->signer->sign($request, $appData);
|
$this->signer->sign($request, $appData);
|
||||||
// TODO: Implement this
|
$requestData = $this->serializer->toArray($request);
|
||||||
|
$requestHasBinaryData = $this->filter->hasBinaryFromRequestData($requestData);
|
||||||
|
|
||||||
|
if (empty($requestData)) {
|
||||||
|
throw new FactoryException('Empty request data');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($requestHasBinaryData) {
|
||||||
|
return $this->makeMultipartRequest($endpoint, $requestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Request(
|
||||||
|
'GET',
|
||||||
|
new Uri($endpoint . '?' . http_build_query($requestData)),
|
||||||
|
'',
|
||||||
|
self::defaultHeaders()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $endpoint
|
||||||
|
* @param array $contents
|
||||||
|
*
|
||||||
|
* @return \Psr\Http\Message\RequestInterface
|
||||||
|
*/
|
||||||
|
private function makeMultipartRequest(string $endpoint, array $contents): RequestInterface
|
||||||
|
{
|
||||||
|
$prepared = [];
|
||||||
|
|
||||||
|
foreach ($contents as $param => $value) {
|
||||||
|
if ($value instanceof FileItemInterface) {
|
||||||
|
$prepared[] = [
|
||||||
|
'name' => $param,
|
||||||
|
'contents' => $value->getStream(),
|
||||||
|
'filename' => $value->getFileName()
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$prepared[] = [
|
||||||
|
'name' => $param,
|
||||||
|
'contents' => $value
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Request(
|
||||||
|
'POST',
|
||||||
|
new Uri($endpoint),
|
||||||
|
new MultipartStream($prepared),
|
||||||
|
self::defaultHeaders()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function defaultHeaders(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'HTTP_ACCEPT' => 'application/json,application/xml;q=0.9',
|
||||||
|
'HTTP_ACCEPT_CHARSET' => 'utf-8;q=0.7,*;q=0.3',
|
||||||
|
'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7',
|
||||||
|
'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; TopSdk; +http://retailcrm.pro)',
|
||||||
|
'HTTP_HOST' => '127.0.0.1',
|
||||||
|
'QUERY_STRING' => '',
|
||||||
|
'REMOTE_ADDR' => '127.0.0.1',
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
'REQUEST_SCHEME' => 'http',
|
||||||
|
'REQUEST_TIME' => time(),
|
||||||
|
'REQUEST_TIME_FLOAT' => microtime(true),
|
||||||
|
'REQUEST_URI' => '',
|
||||||
|
'SCRIPT_NAME' => '',
|
||||||
|
'SERVER_NAME' => 'localhost',
|
||||||
|
'SERVER_PORT' => 80,
|
||||||
|
'SERVER_PROTOCOL' => 'HTTP/1.1',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
src/Factory/SerializationContextFactory.php
Normal file
37
src/Factory/SerializationContextFactory.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category SerializationContextFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Factory;
|
||||||
|
|
||||||
|
use JMS\Serializer\ContextFactory\SerializationContextFactoryInterface;
|
||||||
|
use JMS\Serializer\SerializationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SerializationContextFactory
|
||||||
|
*
|
||||||
|
* @category SerializationContextFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class SerializationContextFactory implements SerializationContextFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return \JMS\Serializer\SerializationContext
|
||||||
|
*/
|
||||||
|
public function createSerializationContext(): SerializationContext
|
||||||
|
{
|
||||||
|
return SerializationContext::create()->setSerializeNull(false);
|
||||||
|
}
|
||||||
|
}
|
139
src/Factory/SerializerFactory.php
Normal file
139
src/Factory/SerializerFactory.php
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category SerializerFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Factory;
|
||||||
|
|
||||||
|
use JMS\Serializer\GraphNavigatorInterface;
|
||||||
|
use JMS\Serializer\Handler\HandlerRegistry;
|
||||||
|
use JMS\Serializer\Serializer;
|
||||||
|
use JMS\Serializer\SerializerBuilder;
|
||||||
|
use JMS\Serializer\SerializerInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use RetailCrm\Component\Constants;
|
||||||
|
use RetailCrm\Interfaces\FactoryInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class SerializerFactory
|
||||||
|
*
|
||||||
|
* @category SerializerFactory
|
||||||
|
* @package RetailCrm\Factory
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class SerializerFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Psr\Container\ContainerInterface $container
|
||||||
|
*/
|
||||||
|
private $container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SerializerFactory constructor.
|
||||||
|
*
|
||||||
|
* @param \Psr\Container\ContainerInterface $container
|
||||||
|
*/
|
||||||
|
public function __construct(ContainerInterface $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Psr\Container\ContainerInterface $container
|
||||||
|
*
|
||||||
|
* @return \RetailCrm\Factory\SerializerFactory
|
||||||
|
*/
|
||||||
|
public static function withContainer(ContainerInterface $container): FactoryInterface
|
||||||
|
{
|
||||||
|
return new self($container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \JMS\Serializer\Serializer
|
||||||
|
*/
|
||||||
|
public function create(): Serializer
|
||||||
|
{
|
||||||
|
$container = $this->container;
|
||||||
|
|
||||||
|
return SerializerBuilder::create()
|
||||||
|
->configureHandlers(function(HandlerRegistry $registry) use ($container) {
|
||||||
|
$returnNull = function($visitor, $obj, array $type) {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
$returnSame = function($visitor, $obj, array $type) {
|
||||||
|
return $obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
||||||
|
'RequestDtoInterface',
|
||||||
|
'json',
|
||||||
|
function($visitor, $obj, array $type) use ($container) {
|
||||||
|
/** @var SerializerInterface $serializer */
|
||||||
|
$serializer = $container->get(Constants::SERIALIZER);
|
||||||
|
|
||||||
|
return $serializer->serialize($obj, 'json');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
||||||
|
'RequestDtoInterface',
|
||||||
|
'json',
|
||||||
|
$returnNull
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
||||||
|
'RequestDtoInterface',
|
||||||
|
'xml',
|
||||||
|
function($visitor, $obj, array $type) use ($container) {
|
||||||
|
/** @var SerializerInterface $serializer */
|
||||||
|
$serializer = $container->get(Constants::SERIALIZER);
|
||||||
|
|
||||||
|
return $serializer->serialize($obj, 'xml');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
||||||
|
'RequestDtoInterface',
|
||||||
|
'xml',
|
||||||
|
$returnNull
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
||||||
|
'FileItemInterface',
|
||||||
|
'json',
|
||||||
|
$returnSame
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
||||||
|
'FileItemInterface',
|
||||||
|
'json',
|
||||||
|
$returnNull
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_SERIALIZATION,
|
||||||
|
'FileItemInterface',
|
||||||
|
'xml',
|
||||||
|
$returnSame
|
||||||
|
);
|
||||||
|
$registry->registerHandler(
|
||||||
|
GraphNavigatorInterface::DIRECTION_DESERIALIZATION,
|
||||||
|
'FileItemInterface',
|
||||||
|
'xml',
|
||||||
|
$returnNull
|
||||||
|
);
|
||||||
|
})->addDefaultHandlers()
|
||||||
|
->setSerializationContextFactory(new SerializationContextFactory())
|
||||||
|
->build();
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHP version 7.3
|
|
||||||
*
|
|
||||||
* @category UploadedFileFactory
|
|
||||||
* @package RetailCrm\Factory
|
|
||||||
* @author RetailCRM <integration@retailcrm.ru>
|
|
||||||
* @license MIT
|
|
||||||
* @link http://retailcrm.ru
|
|
||||||
* @see http://help.retailcrm.ru
|
|
||||||
*/
|
|
||||||
namespace RetailCrm\Factory;
|
|
||||||
|
|
||||||
use Psr\Http\Message\StreamFactoryInterface;
|
|
||||||
use Psr\Http\Message\UploadedFileFactoryInterface as BaseUploadedFileFactoryInterface;
|
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
|
||||||
use RetailCrm\Interfaces\UploadedFileFactoryInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class UploadedFileFactory
|
|
||||||
*
|
|
||||||
* @category UploadedFileFactory
|
|
||||||
* @package RetailCrm\Factory
|
|
||||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
|
||||||
* @license MIT
|
|
||||||
* @link http://retailcrm.ru
|
|
||||||
* @see https://help.retailcrm.ru
|
|
||||||
*/
|
|
||||||
class UploadedFileFactory implements UploadedFileFactoryInterface
|
|
||||||
{
|
|
||||||
/** @var BaseUploadedFileFactoryInterface $baseFactory */
|
|
||||||
private $baseFactory;
|
|
||||||
|
|
||||||
/** @var StreamFactoryInterface $streamFactory */
|
|
||||||
private $streamFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UploadedFileFactory constructor.
|
|
||||||
*
|
|
||||||
* @param \Psr\Http\Message\UploadedFileFactoryInterface $baseFactory
|
|
||||||
* @param \Psr\Http\Message\StreamFactoryInterface $streamFactory
|
|
||||||
*/
|
|
||||||
public function __construct(BaseUploadedFileFactoryInterface $baseFactory, StreamFactoryInterface $streamFactory)
|
|
||||||
{
|
|
||||||
$this->baseFactory = $baseFactory;
|
|
||||||
$this->streamFactory = $streamFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $fileName
|
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\UploadedFileInterface
|
|
||||||
*/
|
|
||||||
public function create(string $fileName): UploadedFileInterface
|
|
||||||
{
|
|
||||||
return $this->baseFactory->createUploadedFile($this->streamFactory->createStreamFromFile($fileName));
|
|
||||||
}
|
|
||||||
}
|
|
42
src/Interfaces/FileItemFactoryInterface.php
Normal file
42
src/Interfaces/FileItemFactoryInterface.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category FactoryInterface
|
||||||
|
* @package RetailCrm\Interfaces
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace RetailCrm\Interfaces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface FileItemFactoryInterface
|
||||||
|
*
|
||||||
|
* @category FileItemFactoryInterface
|
||||||
|
* @package RetailCrm\Interfaces
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
interface FileItemFactoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $fileName Name without path
|
||||||
|
* @param string $contents
|
||||||
|
*
|
||||||
|
* @return FileItemInterface
|
||||||
|
*/
|
||||||
|
public function fromString(string $fileName, string $contents): FileItemInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $fileName Name with or without path
|
||||||
|
*
|
||||||
|
* @return FileItemInterface
|
||||||
|
*/
|
||||||
|
public function fromFile(string $fileName): FileItemInterface;
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
/**
|
/**
|
||||||
* PHP version 7.3
|
* PHP version 7.3
|
||||||
*
|
*
|
||||||
* @category UploadedFileFactoryInterface
|
* @category FileItemInterface
|
||||||
* @package RetailCrm\Interfaces
|
* @package RetailCrm\Interfaces
|
||||||
* @author RetailCRM <integration@retailcrm.ru>
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
* @license MIT
|
* @license MIT
|
||||||
@ -13,24 +13,27 @@
|
|||||||
|
|
||||||
namespace RetailCrm\Interfaces;
|
namespace RetailCrm\Interfaces;
|
||||||
|
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface UploadedFileFactoryInterface
|
* Interface FileItemInterface
|
||||||
*
|
*
|
||||||
* @category UploadedFileFactoryInterface
|
* @category FileItemInterface
|
||||||
* @package RetailCrm\Interfaces
|
* @package RetailCrm\Interfaces
|
||||||
* @author RetailDriver LLC <integration@retailcrm.ru>
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @link http://retailcrm.ru
|
* @link http://retailcrm.ru
|
||||||
* @see https://help.retailcrm.ru
|
* @see https://help.retailcrm.ru
|
||||||
*/
|
*/
|
||||||
interface UploadedFileFactoryInterface
|
interface FileItemInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string $fileName
|
* @return string
|
||||||
*
|
|
||||||
* @return \Psr\Http\Message\UploadedFileInterface
|
|
||||||
*/
|
*/
|
||||||
public function create(string $fileName): UploadedFileInterface;
|
public function getFileName(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Psr\Http\Message\StreamInterface
|
||||||
|
*/
|
||||||
|
public function getStream(): StreamInterface;
|
||||||
}
|
}
|
28
src/Interfaces/RequestDtoInterface.php
Normal file
28
src/Interfaces/RequestDtoInterface.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category RequestDtoInterface
|
||||||
|
* @package RetailCrm\Interfaces
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace RetailCrm\Interfaces;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface RequestDtoInterface
|
||||||
|
*
|
||||||
|
* @category RequestDtoInterface
|
||||||
|
* @package RetailCrm\Interfaces
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
interface RequestDtoInterface
|
||||||
|
{
|
||||||
|
}
|
@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
namespace RetailCrm\Interfaces;
|
namespace RetailCrm\Interfaces;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
use RetailCrm\Model\Request\BaseRequest;
|
use RetailCrm\Model\Request\BaseRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,10 +29,17 @@ use RetailCrm\Model\Request\BaseRequest;
|
|||||||
interface RequestFactoryInterface
|
interface RequestFactoryInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param \RetailCrm\Model\Request\BaseRequest $request
|
* @param string $endpoint
|
||||||
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
* @param \RetailCrm\Model\Request\BaseRequest $request
|
||||||
|
* @param \RetailCrm\Interfaces\AppDataInterface $appData
|
||||||
|
* @param \RetailCrm\Interfaces\AuthenticatorInterface $authenticator
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return RequestInterface
|
||||||
*/
|
*/
|
||||||
public function fromModel(BaseRequest $request, AppDataInterface $appData);
|
public function fromModel(
|
||||||
|
string $endpoint,
|
||||||
|
BaseRequest $request,
|
||||||
|
AppDataInterface $appData,
|
||||||
|
AuthenticatorInterface $authenticator
|
||||||
|
): RequestInterface;
|
||||||
}
|
}
|
||||||
|
63
src/Model/FileItem.php
Normal file
63
src/Model/FileItem.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category FileItem
|
||||||
|
* @package RetailCrm\Model
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Model;
|
||||||
|
|
||||||
|
use Psr\Http\Message\StreamInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FileItem
|
||||||
|
*
|
||||||
|
* @category FileItem
|
||||||
|
* @package RetailCrm\Model
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class FileItem implements FileItemInterface
|
||||||
|
{
|
||||||
|
/** @var string $fileName */
|
||||||
|
private $fileName;
|
||||||
|
|
||||||
|
/** @var StreamInterface */
|
||||||
|
private $stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileItem constructor.
|
||||||
|
*
|
||||||
|
* @param string $fileName
|
||||||
|
* @param \Psr\Http\Message\StreamInterface $stream
|
||||||
|
*/
|
||||||
|
public function __construct(string $fileName, StreamInterface $stream)
|
||||||
|
{
|
||||||
|
$this->fileName = $fileName;
|
||||||
|
$this->stream = $stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFileName(): string
|
||||||
|
{
|
||||||
|
return $this->fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Psr\Http\Message\StreamInterface
|
||||||
|
*/
|
||||||
|
public function getStream(): StreamInterface
|
||||||
|
{
|
||||||
|
return $this->stream;
|
||||||
|
}
|
||||||
|
}
|
59
src/Service/RequestDataFilter.php
Normal file
59
src/Service/RequestDataFilter.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category RequestDataFilter
|
||||||
|
* @package RetailCrm\Service
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Service;
|
||||||
|
|
||||||
|
use RetailCrm\Interfaces\FileItemInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RequestDataFilter
|
||||||
|
*
|
||||||
|
* @category RequestDataFilter
|
||||||
|
* @package RetailCrm\Service
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class RequestDataFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function filterBinaryFromRequestData(array $params): array
|
||||||
|
{
|
||||||
|
return array_filter(array_filter(
|
||||||
|
$params,
|
||||||
|
static function ($item) {
|
||||||
|
return !($item instanceof FileItemInterface);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasBinaryFromRequestData(array $params): bool
|
||||||
|
{
|
||||||
|
foreach ($params as $item) {
|
||||||
|
if ($item instanceof FileItemInterface) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -34,15 +34,22 @@ class RequestSigner implements RequestSignerInterface
|
|||||||
/**
|
/**
|
||||||
* @var SerializerInterface|\JMS\Serializer\Serializer $serializer
|
* @var SerializerInterface|\JMS\Serializer\Serializer $serializer
|
||||||
*/
|
*/
|
||||||
public $serializer;
|
private $serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RequestDataFilter $filter
|
||||||
|
*/
|
||||||
|
private $filter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RequestSigner constructor.
|
* RequestSigner constructor.
|
||||||
*
|
*
|
||||||
* @param \JMS\Serializer\SerializerInterface $serializer
|
* @param \JMS\Serializer\SerializerInterface $serializer
|
||||||
|
* @param \RetailCrm\Service\RequestDataFilter $filter
|
||||||
*/
|
*/
|
||||||
public function __construct(SerializerInterface $serializer)
|
public function __construct(SerializerInterface $serializer, RequestDataFilter $filter)
|
||||||
{
|
{
|
||||||
|
$this->filter = $filter;
|
||||||
$this->serializer = $serializer;
|
$this->serializer = $serializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +62,7 @@ class RequestSigner implements RequestSignerInterface
|
|||||||
public function sign(BaseRequest $request, AppDataInterface $appData): void
|
public function sign(BaseRequest $request, AppDataInterface $appData): void
|
||||||
{
|
{
|
||||||
$stringToBeSigned = '';
|
$stringToBeSigned = '';
|
||||||
$params = $this->getRequestData($request);
|
$params = $this->getDataForSigning($request);
|
||||||
|
|
||||||
foreach ($params as $param => $value) {
|
foreach ($params as $param => $value) {
|
||||||
$stringToBeSigned .= $param . $value;
|
$stringToBeSigned .= $param . $value;
|
||||||
@ -81,9 +88,9 @@ class RequestSigner implements RequestSignerInterface
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getRequestData(BaseRequest $request): array
|
private function getDataForSigning(BaseRequest $request): array
|
||||||
{
|
{
|
||||||
$params = array_filter($this->serializer->toArray($request));
|
$params = $this->filter->filterBinaryFromRequestData($this->serializer->toArray($request));
|
||||||
unset($params['sign']);
|
unset($params['sign']);
|
||||||
ksort($params);
|
ksort($params);
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ namespace RetailCrm\TopClient;
|
|||||||
|
|
||||||
use JMS\Serializer\SerializerInterface;
|
use JMS\Serializer\SerializerInterface;
|
||||||
use Psr\Http\Client\ClientInterface;
|
use Psr\Http\Client\ClientInterface;
|
||||||
|
use RetailCrm\Component\ServiceLocator;
|
||||||
use RetailCrm\Interfaces\AppDataInterface;
|
use RetailCrm\Interfaces\AppDataInterface;
|
||||||
use RetailCrm\Interfaces\AuthenticatorInterface;
|
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||||
use RetailCrm\Interfaces\RequestFactoryInterface;
|
use RetailCrm\Interfaces\RequestFactoryInterface;
|
||||||
@ -63,6 +64,11 @@ class Client
|
|||||||
*/
|
*/
|
||||||
protected $serializer;
|
protected $serializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \RetailCrm\Component\ServiceLocator $serviceLocator
|
||||||
|
*/
|
||||||
|
protected $serviceLocator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client constructor.
|
* Client constructor.
|
||||||
*
|
*
|
||||||
@ -106,4 +112,20 @@ class Client
|
|||||||
{
|
{
|
||||||
$this->requestFactory = $requestFactory;
|
$this->requestFactory = $requestFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \RetailCrm\Component\ServiceLocator $serviceLocator
|
||||||
|
*/
|
||||||
|
public function setServiceLocator(ServiceLocator $serviceLocator): void
|
||||||
|
{
|
||||||
|
$this->serviceLocator = $serviceLocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \RetailCrm\Component\ServiceLocator
|
||||||
|
*/
|
||||||
|
public function getServiceLocator(): ServiceLocator
|
||||||
|
{
|
||||||
|
return $this->serviceLocator;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,15 @@
|
|||||||
|
|
||||||
namespace RetailCrm\Test;
|
namespace RetailCrm\Test;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
|
use RetailCrm\Component\AppData;
|
||||||
|
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
||||||
use RetailCrm\Component\Environment;
|
use RetailCrm\Component\Environment;
|
||||||
use RetailCrm\Factory\ContainerFactory;
|
use RetailCrm\Factory\ContainerFactory;
|
||||||
|
use RetailCrm\Factory\FileItemFactory;
|
||||||
|
use RetailCrm\Interfaces\AppDataInterface;
|
||||||
|
use RetailCrm\Interfaces\AuthenticatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class TestCase
|
* Class TestCase
|
||||||
@ -20,7 +26,12 @@ class TestCase extends \PHPUnit\Framework\TestCase
|
|||||||
{
|
{
|
||||||
private $container;
|
private $container;
|
||||||
|
|
||||||
public function getContainer($recreate = false): ContainerInterface
|
/**
|
||||||
|
* @param bool $recreate
|
||||||
|
*
|
||||||
|
* @return \Psr\Container\ContainerInterface
|
||||||
|
*/
|
||||||
|
protected function getContainer($recreate = false): ContainerInterface
|
||||||
{
|
{
|
||||||
if (null === $this->container || $recreate) {
|
if (null === $this->container || $recreate) {
|
||||||
$this->container = ContainerFactory::withEnv(Environment::TEST)
|
$this->container = ContainerFactory::withEnv(Environment::TEST)
|
||||||
@ -30,4 +41,55 @@ class TestCase extends \PHPUnit\Framework\TestCase
|
|||||||
|
|
||||||
return $this->container;
|
return $this->container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \RetailCrm\Interfaces\AppDataInterface
|
||||||
|
*/
|
||||||
|
protected function getAppData(): AppDataInterface
|
||||||
|
{
|
||||||
|
return AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $appKey
|
||||||
|
* @param string $token
|
||||||
|
*
|
||||||
|
* @return \RetailCrm\Interfaces\AuthenticatorInterface
|
||||||
|
*/
|
||||||
|
protected function getAuthenticator(string $appKey = 'appKey', string $token = 'token'): AuthenticatorInterface
|
||||||
|
{
|
||||||
|
return new TokenAuthenticator($appKey, $token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $signMethod
|
||||||
|
*
|
||||||
|
* @param bool $withFile
|
||||||
|
*
|
||||||
|
* @return \RetailCrm\Test\TestSignerRequest
|
||||||
|
*/
|
||||||
|
protected function getTestRequest(string $signMethod, bool $withFile = false): TestSignerRequest
|
||||||
|
{
|
||||||
|
$request = new TestSignerRequest();
|
||||||
|
$request->method = 'aliexpress.solution.order.fulfill';
|
||||||
|
$request->appKey = '12345678';
|
||||||
|
$request->session = 'test';
|
||||||
|
$request->timestamp = DateTime::createFromFormat('Y-m-d H:i:s', '2016-01-01 12:00:00');
|
||||||
|
$request->signMethod = $signMethod;
|
||||||
|
$request->serviceName = 'SPAIN_LOCAL_CORREOS';
|
||||||
|
$request->outRef = '1000006270175804';
|
||||||
|
$request->sendType = 'all';
|
||||||
|
$request->logisticsNo = 'ES2019COM0000123456';
|
||||||
|
|
||||||
|
if ($withFile) {
|
||||||
|
/** @var FileItemFactory $factory */
|
||||||
|
$factory = $this->getContainer()->get(FileItemFactory::class);
|
||||||
|
$request->document = $factory->fromString(
|
||||||
|
'file.txt',
|
||||||
|
'The quick brown fox jumps over the lazy dog'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
namespace RetailCrm\Test;
|
namespace RetailCrm\Test;
|
||||||
|
|
||||||
use JMS\Serializer\Annotation as JMS;
|
use JMS\Serializer\Annotation as JMS;
|
||||||
|
use Psr\Http\Message\UploadedFileInterface;
|
||||||
|
use RetailCrm\Interfaces\FileItemInterface;
|
||||||
use RetailCrm\Model\Request\BaseRequest;
|
use RetailCrm\Model\Request\BaseRequest;
|
||||||
use Symfony\Component\Validator\Constraints as Assert;
|
use Symfony\Component\Validator\Constraints as Assert;
|
||||||
|
|
||||||
@ -63,4 +65,12 @@ class TestSignerRequest extends BaseRequest
|
|||||||
* @Assert\NotBlank()
|
* @Assert\NotBlank()
|
||||||
*/
|
*/
|
||||||
public $logisticsNo;
|
public $logisticsNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var FileItemInterface $document
|
||||||
|
*
|
||||||
|
* @JMS\Type("FileItemInterface")
|
||||||
|
* @JMS\SerializedName("document")
|
||||||
|
*/
|
||||||
|
public $document;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ namespace RetailCrm\Tests\Component;
|
|||||||
|
|
||||||
use RetailCrm\Component\AppData;
|
use RetailCrm\Component\AppData;
|
||||||
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
||||||
|
use RetailCrm\Component\ServiceLocator;
|
||||||
use RetailCrm\Factory\ClientFactory;
|
use RetailCrm\Factory\ClientFactory;
|
||||||
use RetailCrm\Test\TestCase;
|
use RetailCrm\Test\TestCase;
|
||||||
use RetailCrm\TopClient\Client;
|
use RetailCrm\TopClient\Client;
|
||||||
@ -38,5 +39,6 @@ class ClientFactoryTest extends TestCase
|
|||||||
->create();
|
->create();
|
||||||
|
|
||||||
self::assertInstanceOf(Client::class, $client);
|
self::assertInstanceOf(Client::class, $client);
|
||||||
|
self::assertInstanceOf(ServiceLocator::class, $client->getServiceLocator());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
tests/RetailCrm/Tests/Factory/RequestFactoryTest.php
Normal file
67
tests/RetailCrm/Tests/Factory/RequestFactoryTest.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PHP version 7.3
|
||||||
|
*
|
||||||
|
* @category RequestFactoryTest
|
||||||
|
* @package RetailCrm\Tests\Factory
|
||||||
|
* @author RetailCRM <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see http://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
namespace RetailCrm\Tests\Factory;
|
||||||
|
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
use RetailCrm\Component\Constants;
|
||||||
|
use RetailCrm\Factory\RequestFactory;
|
||||||
|
use RetailCrm\Component\AppData;
|
||||||
|
use RetailCrm\Test\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RequestFactoryTest
|
||||||
|
*
|
||||||
|
* @category RequestFactoryTest
|
||||||
|
* @package RetailCrm\Tests\Factory
|
||||||
|
* @author RetailDriver LLC <integration@retailcrm.ru>
|
||||||
|
* @license MIT
|
||||||
|
* @link http://retailcrm.ru
|
||||||
|
* @see https://help.retailcrm.ru
|
||||||
|
*/
|
||||||
|
class RequestFactoryTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testFromModelGet(): void
|
||||||
|
{
|
||||||
|
/** @var RequestFactory $factory */
|
||||||
|
$factory = $this->getContainer()->get(RequestFactory::class);
|
||||||
|
$request = $factory->fromModel(
|
||||||
|
AppData::OVERSEAS_ENDPOINT,
|
||||||
|
$this->getTestRequest(Constants::SIGN_TYPE_HMAC),
|
||||||
|
$this->getAppData(),
|
||||||
|
$this->getAuthenticator()
|
||||||
|
);
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$contents = $request->getBody()->getContents();
|
||||||
|
|
||||||
|
self::assertEmpty($contents);
|
||||||
|
self::assertNotEmpty($uri->getQuery());
|
||||||
|
self::assertNotFalse(stripos($uri->getQuery(), 'SPAIN_LOCAL_CORREOS'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFromModelPost(): void
|
||||||
|
{
|
||||||
|
/** @var RequestFactory $factory */
|
||||||
|
$factory = $this->getContainer()->get(RequestFactory::class);
|
||||||
|
$request = $factory->fromModel(
|
||||||
|
AppData::OVERSEAS_ENDPOINT,
|
||||||
|
$this->getTestRequest(Constants::SIGN_TYPE_HMAC, true),
|
||||||
|
$this->getAppData(),
|
||||||
|
$this->getAuthenticator()
|
||||||
|
);
|
||||||
|
$uri = $request->getUri();
|
||||||
|
$contents = $request->getBody()->getContents();
|
||||||
|
|
||||||
|
self::assertEmpty($uri->getQuery());
|
||||||
|
self::assertNotFalse(stripos($contents, 'The quick brown fox jumps over the lazy dog'));
|
||||||
|
}
|
||||||
|
}
|
@ -12,9 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
namespace RetailCrm\Tests\Service;
|
namespace RetailCrm\Tests\Service;
|
||||||
|
|
||||||
use DateTime;
|
|
||||||
use RetailCrm\Component\AppData;
|
use RetailCrm\Component\AppData;
|
||||||
use RetailCrm\Component\Authenticator\TokenAuthenticator;
|
|
||||||
use RetailCrm\Component\Constants;
|
use RetailCrm\Component\Constants;
|
||||||
use RetailCrm\Interfaces\AppDataInterface;
|
use RetailCrm\Interfaces\AppDataInterface;
|
||||||
use RetailCrm\Interfaces\RequestSignerInterface;
|
use RetailCrm\Interfaces\RequestSignerInterface;
|
||||||
@ -52,40 +50,29 @@ class RequestSignerTest extends TestCase
|
|||||||
|
|
||||||
public function signDataProvider(): array
|
public function signDataProvider(): array
|
||||||
{
|
{
|
||||||
$appData = AppData::create(AppData::OVERSEAS_ENDPOINT, 'appKey', 'helloworld');
|
$appData = $this->getAppData();
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_MD5),
|
$this->getTestRequest(Constants::SIGN_TYPE_MD5),
|
||||||
$appData,
|
$appData,
|
||||||
'4BC79C5FAA1B5E254E95A97E65BACEAB'
|
'4BC79C5FAA1B5E254E95A97E65BACEAB'
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$this->getRequestWithSignMethod(Constants::SIGN_TYPE_HMAC),
|
$this->getTestRequest(Constants::SIGN_TYPE_HMAC),
|
||||||
|
$appData,
|
||||||
|
'497FA7FCAD98F4F335EFAE2451F8291D'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->getTestRequest(Constants::SIGN_TYPE_MD5, true),
|
||||||
|
$appData,
|
||||||
|
'4BC79C5FAA1B5E254E95A97E65BACEAB'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
$this->getTestRequest(Constants::SIGN_TYPE_HMAC, true),
|
||||||
$appData,
|
$appData,
|
||||||
'497FA7FCAD98F4F335EFAE2451F8291D'
|
'497FA7FCAD98F4F335EFAE2451F8291D'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $signMethod
|
|
||||||
*
|
|
||||||
* @return \RetailCrm\Test\TestSignerRequest
|
|
||||||
*/
|
|
||||||
private function getRequestWithSignMethod(string $signMethod): TestSignerRequest
|
|
||||||
{
|
|
||||||
$request = new TestSignerRequest();
|
|
||||||
$request->method = 'aliexpress.solution.order.fulfill';
|
|
||||||
$request->appKey = '12345678';
|
|
||||||
$request->session = 'test';
|
|
||||||
$request->timestamp = DateTime::createFromFormat('Y-m-d H:i:s', '2016-01-01 12:00:00');
|
|
||||||
$request->signMethod = $signMethod;
|
|
||||||
$request->serviceName = 'SPAIN_LOCAL_CORREOS';
|
|
||||||
$request->outRef = '1000006270175804';
|
|
||||||
$request->sendType = 'all';
|
|
||||||
$request->logisticsNo = 'ES2019COM0000123456';
|
|
||||||
|
|
||||||
return $request;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user