Commit d52ea433 authored by Henri-Damien LAURENT's avatar Henri-Damien LAURENT
Browse files

refactoring

parent ba7e79a4
Pipeline #10309 passed with stage
in 40 seconds
......@@ -4,7 +4,8 @@ test:php7_latest:
- apt update -yqq && apt install git zip -yqq
- git submodule init
- git submodule update
- ./vendor/bin/phpunit -c tests/phpunit.xml
- php --version
- ./vendor/bin/phpunit -c tests/phpunit.xml --testdox
except:
- tags
tags:
......
......@@ -16,7 +16,7 @@ $app->add(function($request, $handler)
{
$database_settings = require __DIR__ . '/../conf/database.php';
Connections::getInstance()->setDefault(new Connection(new Configuration($settings)));
Connections::getInstance()->setDefault(new Connection(new Configuration($database_settings)));
Loader::defaultTo(SqlStrategy::class);
return $handler->handle($request);
......
<?php
namespace Pellicule\Controllers;
use Slim\Routing\RouteContext;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use Fig\Http\Message\StatusCodeInterface;
use Pellicule\Models\Record;
use Pellicule\Providers\Provider;
use Pellicule\Providers\FetchRecordResult;
class Media {
public function __invoke($request, $response, $args) {
$fetch_result = $this->_fetchRecord($request, $args);
$fetch_result = Provider::newProvider($request, $this->_processArgs($args))
->fetchRecord();
if ($fetch_result->hasError()) {
$response->getBody()->write(json_encode(['error' => $fetch_result->getErrorCode()]));
return $response
->withStatus($fetch_result->getStatusCode())
->withHeader('Content-Type', 'application/json');
}
$response->getBody()->write($fetch_result->asJSON());
$record = $fetch_result->getRecord();
$record->save();
$response->getBody()->write($record->asJSON());
return $response->withHeader('Content-Type', 'application/json');
}
protected function _fetchRecord($request, $args) {
$findargs = $this->_processParams($args);
if ($record = Record::findFirstBy($findargs))
return (new FetchRecordResult())->beSuccess($record);
if (!$request->hasHeader('Authorization'))
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_NOT_FOUND, 'record not found');
return $this->_fetchRecordFromProviders($request, $findargs);
}
protected function _fetchRecordFromProviders($request, $args){
$credentials = array_filter($this->_parseAuthorizationHeader($request));
if (!$credentials)
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED, 'no_valid_authentication_information_provided');
$provider = Provider::newProvider($credentials[0]);
return $provider->fetchRecord($this->_processParams($args));
}
protected function _parseAuthorizationHeader($request){
return (new \Storm\Model\Collection($request->getHeader('Authorization')))
->select(function($header)
{
return ($header && (strpos('Pellicule ', $header) == 0));
})
->collect([$this, 'decodeAuthorizationHeader'])->getArrayCopy();
return $response
->withStatus($fetch_result->getStatusCode())
->withHeader('Content-Type', 'application/json');
}
public function decodeAuthorizationHeader($string){
return json_decode(base64_decode(str_replace('Pellicule ', '', $string)), true);
}
protected function _processParams($args){
protected function _processArgs($args) {
if (!$args)
return;
return [];
if (isset($args['isbn']))
return ['isbn' => $args['isbn']];
......
......@@ -14,28 +14,29 @@ class FileSystem {
}
public function download($media) {
$directory = static::$_base_path . $media->getLocation();
if (false === \file_exists($directory))
\mkdir($directory, 0755, true);
public function fileExists($path) {
return \file_exists(static::$_base_path . $path);
}
if (!$url_to_fetch = $media->getUrl())
public function download($url, $path) {
if (!$url || !$path)
return;
$client = $this->newHttpClient();
$directory = pathinfo(static::$_base_path . $path, PATHINFO_DIRNAME);
if (false === \file_exists($directory))
\mkdir($directory, 0755, true);
if (false === ($local_file = fopen($directory . '/' . $media->getLocalFileName() , 'w')))
return;
// can throw exception on invalid url
$request = (new RequestFactory())->createRequest('GET', $url);
if (false === ($local_file = fopen(static::$_base_path . $path , 'w')))
return;
$request = (new RequestFactory())->createRequest('GET', $url_to_fetch);
if ($request)
$client->sendRequest($request,['curl' => ['CURLOPT_FILE', $local_file ]] );
$this->newHttpClient()
->sendRequest($request,
['curl' => ['CURLOPT_FILE', $local_file]]);
fclose($local_file);
// $media->resizeTo('thumbnail');
// $directory = static::$_base_path . $media->getLocation();
//
}
}
......@@ -5,16 +5,19 @@ use \Storm\Model\ModelAbstract;
class Media extends ModelAbstract {
protected static $_file_system;
const
SIZE_FULL = 'fullsize',
TYPE_COVER = 'cover',
TYPE_BACKCOVER = 'back_cover';
protected
$_size;
protected static $_file_system;
protected
$_table_name = 'media',
$_belongs_to = [ 'record' => ['model' => Record::class ] ];
/** @category testing */
public static function setFileSystem($filesystem) {
static::$_file_system = $filesystem;
}
......@@ -34,39 +37,61 @@ class Media extends ModelAbstract {
public function afterSave() {
$this->getFileSystem()->download($this);
if (!$this->fileExists())
$this->getFileSystem()->download($this->getUrl(), $this->getFilePath());
}
public function fileExists() {
return $this->isNew()
? false
: $this->getFileSystem()->fileExists($this->getFilePath());
}
public function getSize(){
return (isset($this->_size))
? $this->_size
: 'fullsize';
public function validate() {
$this->checkAttribute('type',
in_array($this->getType(),
[static::TYPE_COVER, static::TYPE_BACKCOVER]),
'unknown type');
}
public function setSize($string){
$this->_size = $string;
return $this;
public function getSize() {
return static::SIZE_FULL;
}
public function splitId(){
return array_slice(str_split(sprintf("%04d", $this->getId()))
, 0, 4 );
public function getFilePath() {
return $this->getLocation() . $this->getLocalFileName();
}
public function getLocation(){
return '/' . implode('/', array_merge([$this->getSize(), $this->getType()], $this->splitId())) .'/';
public function splitId() {
return array_slice(str_split(sprintf('%04d', $this->getId())),
0, 4);
}
public function getLocalFileName(){
return $this->getId() . '.' . $this->getExtension();
protected function getLocation() {
return $this->isNew()
? ''
: '/' . implode('/', array_merge([$this->getSize(),
$this->getType()],
$this->splitId())) . '/';
}
public function getExtension(){
return pathinfo( $this->getUrl() , PATHINFO_EXTENSION );
protected function getLocalFileName() {
return $this->isNew()
? ''
: $this->getId() . '.' . $this->getExtension();
}
protected function getExtension() {
return $this->hasUrl()
? pathinfo($this->getUrl(), PATHINFO_EXTENSION)
: '';
}
}
......@@ -37,12 +37,12 @@ class Electre extends Provider {
}
public function fetchRecord($search_request) {
try{
public function fetchRecord() {
try {
$response = $this->_getToken($this->_client_id, $this->_client_secret);
} catch(\Exception $exception) {
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT
,
,
'no_answer_from_gateway');
}
......@@ -68,13 +68,12 @@ class Electre extends Provider {
$token = $json_response->access_token;
try{
$response = $this->_getAllLinks($token, $this->_getIsbnOrEan($search_request));
$response = $this->_getAllLinks($token, $this->_getIsbnOrEan($this->_search_args));
} catch(\Exception $exception) {
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT,
'no_answer_from_gateway');
}
if ($response->getStatusCode() != StatusCodeInterface::STATUS_OK)
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_NOT_FOUND,
'ean_not_found');
......@@ -100,13 +99,13 @@ class Electre extends Provider {
return (new \Pellicule\Models\Media())
->setProvider($this->providerName())
->setUrl($link->href)
->setType( (($link->rel=='coverCopy')
->setType( (($link->rel == 'coverCopy')
? 'back_cover'
: $link->rel));
});
$record = (new Record())
->updateAttributes($search_request)
->updateAttributes($this->_search_args)
->setMedia($medialist);
return (new FetchRecordResult())->beSuccess($record);
......
<?php
namespace Pellicule\Providers;
class Error extends Provider {
protected $_code, $_label;
public function __construct($code, $label) {
$this->_code = $code;
$this->_label = $label;
}
public function fetchRecord() {
return (new FetchRecordResult())->beError($this->_code, $this->_label);
}
}
......@@ -8,27 +8,25 @@ class FetchRecordResult {
$_record;
public function beError($status_code, $error_code){
public function beError($status_code, $error_code) {
$this->_status_code = $status_code;
$this->_error_code = $error_code;
return $this;
}
public function beSuccess($record){
$this->_record = $record;
$this->_record->save();
$this->_status_code = 200;
return $this;
}
public function getRecord(){
return $this->_record;
}
public function hasError(){
return !empty($this->_status_code);
}
public function getErrorCode(){
return $this->_error_code;
public function asJSON() {
return $this->_record
? $this->_record->asJSON()
: json_encode(['error' => $this->_error_code]);
}
public function getStatusCode(){
......
<?php
namespace Pellicule\Providers;
class Local extends Provider {
protected $_record;
public function __construct($record) {
$this->_record = $record;
}
public function fetchRecord() {
return (new FetchRecordResult())->beSuccess($this->_record);
}
}
......@@ -35,10 +35,9 @@ class Orb extends Provider {
}
public function fetchRecord($search_request) {
$browser = static::newHttpClient();
$ean = $this->_getIsbnOrEan($search_request);
public function fetchRecord() {
$browser = static::newHttpClient();
$ean = $this->_getIsbnOrEan($this->_search_args);
try{
$response = $browser->get( static::END_POINT . '/v1/products?sort=ean_asc&eans=' . $ean,
......@@ -77,12 +76,9 @@ class Orb extends Provider {
}
$record = (new Record())
->updateAttributes($search_request)
->updateAttributes($this->_search_args)
->setMedia($medialist);
return (new FetchRecordResult())->beSuccess($record);
}
}
......@@ -2,23 +2,58 @@
namespace Pellicule\Providers;
use \Pellicule\HttpClientAware;
use \Pellicule\Models\Record;
use \Pellicule\Providers\FetchRecordResult;
use Fig\Http\Message\StatusCodeInterface;
abstract class Provider {
use HttpClientAware;
abstract public function providerName();
protected $_search_args = [];
public static function newProvider($request, $args) {
return static::_realNewProvider($request, $args)
->setSearchArgs($args);
}
protected static function _realNewProvider($request, $args) {
if (!$args)
return new Error(StatusCodeInterface::STATUS_BAD_REQUEST,
'no valid params provided');
if ($record = Record::findFirstBy($args))
return new Local($record);
if (!$request->hasHeader('Authorization'))
return new Error(StatusCodeInterface::STATUS_NOT_FOUND, 'record not found');
$credentials = array_filter((new \Storm\Collection($request->getHeader('Authorization')))
->select(function($header)
{
return ($header && 'Pellicule ' == substr($header, 0, 10));
})
->collect(function($string)
{
return json_decode(base64_decode(str_replace('Pellicule ', '', $string)), true);
})
->getArrayCopy());
abstract public function fetchRecord($search_args);
if (!$credentials)
return new Error(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED,
'no_valid_authentication_information_provided');
static public function newProvider($credentials) {
if ($credentials['provider'] == 'electre')
$first_credentials = array_shift($credentials);
if ($first_credentials['provider'] == 'electre')
return new Electre($credentials);
if ($credentials['provider'] == 'orb')
if ($first_credentials['provider'] == 'orb')
return new Orb($credentials);
return null;
return new Error(StatusCodeInterface::STATUS_BAD_REQUEST,
'no valid params provided');
}
......@@ -34,4 +69,18 @@ abstract class Provider {
return $search_args['isbn'];
}
public function providerName() {
return static::class;
}
public function setSearchArgs($args) {
$this->_search_args = $args;
return $this;
}
abstract public function fetchRecord();
}
\ No newline at end of file
<?php
namespace Pellicule\Tests;
class BrowserMockBuilder {
protected $_mock;
public function __construct() {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->_mock = \Storm\Test\ObjectWrapper::mock('from ' . $stack[0]['file'] . ':' . $stack[0]['line']);
}
public function expectHttpQuery($request, $response) {
$request = array_merge(['type' => 'get', 'url' => '', 'headers' => [], 'body' => []],
$request);
$with_params = [];
$with_params[] = $request['url'];
$sith_params[] = $request['headers'];
if ($request['type'] == 'post')
$with_params[] = http_build_query($request['body']);
$this
->_mock
->whenCalled($request['type'])
->with($with_params)
->answers($this->_forgePSR7Response($response));
return $this;
}
public function expectPost($post, $response) {
$post = array_merge(['url' => '', 'headers' => [], 'body' => []],
$post);
$this
->_mock
->whenCalled('post')
->with($post['url'],
$post['headers'],
http_build_query($post['body']))
->answers($this->_forgePSR7Response($response));
return $this;
}
public function expectGet($get, $response) {
$get = array_merge(['url' => '', 'headers' => []],
$get);
$this
->_mock
->whenCalled('get')
->with($get['url'],
$get['headers'])
->answers($this->_forgePSR7Response($response));
return $this;
}
public function getMock() {
return $this->_mock;
}
protected function _forgePSR7Response($response) {
$response = array_merge(['status' => 200, 'headers' => null, 'content' => ''],
$response);
return new \Slim\Psr7\Response($response['status'],
$response['headers'],
(new \Slim\Psr7\Factory\StreamFactory())->createStream($response['content']));
}
}
......@@ -7,46 +7,38 @@ use Pellicule\Models\Record;
use Pellicule\Models\Media;
use Pellicule\Providers\Electre;
use Pellicule\Providers\Orb;
use Pellicule\FileSystem;
use \org\bovigo\vfs\vfsStream;
class FileSystemTest extends TestCase {
protected $_root;
public function setUp(){
$this->_root = vfsStream::setup('myDir');
FileSystem::setBasePath(vfsStream::url('myDir'));
$mock_browser = (new BrowserMockBuilder())
->expectGet(['url' => 'http://image.org/city.jpg',
],
['content' => file_get_contents(realpath(dirname(__FILE__)) . '/' .'imagefullsize.jpg')])
->getMock()
->beStrict();
$app = AppFactory::create();
Record::newInstance(['id' => 1,
'isbn' => '2259228194',
'ean' => '9782259228190',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb445155569'])
->assertSave();
Media::newInstance(['id'=> 135,
'record_id'=> 1,
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
])
->assertSave();
parent::setUp();
Media::setFileSystem(null);
$this->_http_client
->whenCalled('get')
->answers($this->_forgePSR7Response(['content' => file_get_contents(realpath(dirname(__FILE__)) . '/' .'imagefullsize.jpg')]));
$this->fixture(Record::class,
['id' => 1,
'isbn' => '2259228194',
'ean' => '9782259228190',