Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Open sidebar
afi
Pellicule
Commits
ccf136c0
Commit
ccf136c0
authored
Mar 17, 2022
by
Patrick Barroca
😁
Browse files
dev #152400 : remote providers refactoring + tests maintenance
parent
c3b9aec9
Pipeline
#16816
passed with stage
in 35 seconds
Changes
15
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
400 additions
and
505 deletions
+400
-505
src/FileSystem.php
src/FileSystem.php
+0
-28
src/Models/Media.php
src/Models/Media.php
+1
-14
src/Providers/ElectreNg.php
src/Providers/ElectreNg.php
+24
-67
src/Providers/Error.php
src/Providers/Error.php
+69
-4
src/Providers/FetchRecordResult.php
src/Providers/FetchRecordResult.php
+2
-3
src/Providers/Local.php
src/Providers/Local.php
+2
-2
src/Providers/Orb.php
src/Providers/Orb.php
+29
-58
src/Providers/Provider.php
src/Providers/Provider.php
+8
-66
src/Providers/RemoteProvider.php
src/Providers/RemoteProvider.php
+120
-0
src/Providers/RemoteProviders.php
src/Providers/RemoteProviders.php
+3
-3
tests/FileSystemTest.php
tests/FileSystemTest.php
+0
-66
tests/MediaElectreNgTest.php
tests/MediaElectreNgTest.php
+10
-13
tests/MediaOrbTest.php
tests/MediaOrbTest.php
+104
-0
tests/MediaTest.php
tests/MediaTest.php
+21
-170
tests/TestCase.php
tests/TestCase.php
+7
-11
No files found.
src/FileSystem.php
View file @
ccf136c0
...
...
@@ -51,26 +51,6 @@ class FileSystem {
}
public
function
download
(
$media
)
{
$url
=
$media
->
getUrl
();
$path
=
$media
->
getFullsize
();
if
(
!
$url
||
!
$path
)
return
;
$request
=
(
new
RequestFactory
())
->
createRequest
(
'GET'
,
$url
);
$request
=
$this
->
_injectProviderHeaders
(
$request
,
$media
);
$client
=
$this
->
newFileGetContents
();
$response
=
$client
->
sendRequest
(
$request
);
if
(
StatusCodeInterface
::
STATUS_OK
!=
(
$status
=
$response
->
getStatusCode
()))
return
;
$this
->
writeMediaFrom
(
$media
,
$response
);
}
public
function
writeMediaFrom
(
$media
,
$response
)
{
if
(
!
$path
=
$media
->
getFullsize
())
return
false
;
...
...
@@ -97,12 +77,4 @@ class FileSystem {
if
(
false
===
$this
->
fileExists
(
$directory
))
$this
->
mkdir
(
$directory
,
0755
,
true
);
}
protected
function
_injectProviderHeaders
(
$request
,
$media
)
{
foreach
(
$media
->
getProviderHeaders
()
as
$key
=>
$value
)
$request
=
$request
->
withHeader
(
$key
,
$value
);
return
$request
;
}
}
src/Models/Media.php
View file @
ccf136c0
...
...
@@ -21,20 +21,7 @@ class Media extends ModelAbstract {
$_default_attribute_values
=
[
'fullsize'
=>
''
,
'created_at'
=>
''
,
'updated_at'
=>
''
],
$_provider_headers
=
[];
public
function
getProviderHeaders
()
{
return
$this
->
_provider_headers
;
}
public
function
setProviderHeaders
(
$headers
)
{
$this
->
_provider_headers
=
$headers
;
return
$this
;
}
'updated_at'
=>
''
];
public
function
getJsonAttributes
()
{
$attribs
=
array_intersect_key
(
$this
->
getRawAttributes
(),
...
...
src/Providers/ElectreNg.php
View file @
ccf136c0
...
...
@@ -8,9 +8,10 @@ use \Pellicule\Models\Record;
use
\
Pellicule\FileSystem
;
class
ElectreNg
extends
Provider
{
class
ElectreNg
extends
Remote
Provider
{
const
ID
=
'electre_ng'
,
TOKEN_URL
=
'https://login.electre-ng.com/auth/realms/electre/protocol/openid-connect/token'
,
API_URL
=
'https://api.electre-ng.com/notices/ean/{ean}'
,
CLIENT_ID
=
'api-client'
,
...
...
@@ -18,63 +19,52 @@ class ElectreNg extends Provider {
ID_KEY
=
'client_id'
,
SECRET_KEY
=
'client_secret'
;
protected
$_id
,
$_secret
;
public
static
function
handles
(
string
$provider_id
)
:
bool
{
// handle previous electre provider code
return
in_array
(
$provider_id
,
[
static
::
ID
,
'electre_rest_2'
]);
}
public
function
__construct
(
$credentials
)
{
public
function
__construct
(
array
$credentials
,
array
$args
)
{
parent
::
__construct
(
$credentials
,
$args
);
$this
->
_id
=
$credentials
[
static
::
ID_KEY
]
??
''
;
$this
->
_secret
=
$credentials
[
static
::
SECRET_KEY
]
??
''
;
}
public
function
fetchRecord
()
{
if
(
!
$this
->
_id
||
!
$this
->
_secret
)
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_UNAUTHORIZED
,
'invalid_credentials'
);
public
function
providerName
()
:
string
{
return
'Electre'
;
}
protected
function
_isValidCredentials
()
:
bool
{
return
$this
->
_id
&&
$this
->
_secret
;
}
if
(
!
$identifier
=
$this
->
_getIsbnOrEan
())
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_BAD_REQUEST
,
'missing_param'
);
protected
function
_coverUrl
(
string
$identifier
)
{
$token_response
=
$this
->
_getToken
();
if
(
$token_response
instanceof
FetchRecordResult
)
return
$token_response
;
$
cover
_url
=
$this
->
_getCoverUrl
(
$identifier
,
$token_response
);
if
(
$cover_url
instanceof
FetchRecordResult
)
return
$cover_url
;
$
api
_url
=
str_replace
(
'{ean}'
,
$identifier
,
static
::
API_URL
);
$headers
=
[
'Authorization'
=>
((
$token_response
->
token_type
??
'Bearer'
)
.
' '
.
$token_response
->
access_token
)]
;
$response
=
$this
->
_withHttpDo
(
fn
(
$http
)
=>
$http
->
get
(
$
cover_url
));
$response
=
$this
->
_withHttpDo
(
fn
(
$http
)
=>
$http
->
get
(
$
api_url
,
$headers
));
if
(
$response
instanceof
FetchRecordResult
)
return
$response
;
$media
=
Media
::
newInstance
([
'provider'
=>
$this
->
providerName
(),
'type'
=>
Media
::
TYPE_COVER
,
'url'
=>
$cover_url
,
'fullsize_from_identifier'
=>
$identifier
,
]);
if
(
!
(
new
FileSystem
)
->
writeMediaFrom
(
$media
,
$response
))
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_INTERNAL_SERVER_ERROR
,
'error_writing_cover'
);
$record
=
Record
::
newInstance
(
array_merge
(
$this
->
_search_args
,
[
'media'
=>
[
$media
]]));
return
$record
->
save
()
?
(
new
FetchRecordResult
)
->
beSuccess
(
$record
)
:
$this
->
_newError
(
StatusCodeInterface
::
STATUS_INTERNAL_SERVER_ERROR
,
'error_saving_record'
);
}
public
function
providerName
()
{
return
'Electre'
;
return
((
$json_response
=
$this
->
_parseJsonResponse
(
$response
))
&&
(
$records
=
$json_response
->
notices
??
[])
&&
(
$cover_url
=
$records
[
0
]
->
imageCouverture
??
false
))
?
$cover_url
:
$this
->
_providerError
();
}
...
...
@@ -92,39 +82,6 @@ class ElectreNg extends Provider {
return
((
$json_response
=
$this
->
_parseJsonResponse
(
$response
))
&&
isset
(
$json_response
->
access_token
))
?
$json_response
:
$this
->
_newError
(
StatusCodeInterface
::
STATUS_BAD_GATEWAY
,
'provider_error'
);
}
protected
function
_getCoverUrl
(
$ean
,
$token_response
)
{
$api_url
=
str_replace
(
'{ean}'
,
$ean
,
static
::
API_URL
);
$headers
=
[
'Authorization'
=>
((
$token_response
->
token_type
??
'Bearer'
)
.
' '
.
$token_response
->
access_token
)];
$response
=
$this
->
_withHttpDo
(
fn
(
$http
)
=>
$http
->
get
(
$api_url
,
$headers
));
if
(
$response
instanceof
FetchRecordResult
)
return
$response
;
return
((
$json_response
=
$this
->
_parseJsonResponse
(
$response
))
&&
(
$records
=
$json_response
->
notices
??
[])
&&
(
$cover_url
=
$records
[
0
]
->
imageCouverture
??
false
))
?
$cover_url
:
$this
->
_newError
(
StatusCodeInterface
::
STATUS_BAD_GATEWAY
,
'provider_error'
);
}
protected
function
_withHttpDo
(
$callback
)
{
try
{
$response
=
$callback
(
static
::
newHttpClient
());
}
catch
(
\
Exception
$e
)
{
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_GATEWAY_TIMEOUT
,
'no_answer_from_gateway'
);
}
return
(
$response
->
getStatusCode
()
===
StatusCodeInterface
::
STATUS_OK
&&
(
$body
=
$response
->
getBody
())
&&
0
<
$body
->
getSize
())
?
$response
:
$this
->
_newError
(
StatusCodeInterface
::
STATUS_BAD_GATEWAY
,
'provider_error'
);
:
$this
->
_providerError
();
}
}
src/Providers/Error.php
View file @
ccf136c0
<?php
namespace
Pellicule\Providers
;
use
\
Fig\Http\Message\StatusCodeInterface
;
class
Error
extends
Provider
{
protected
$_code
,
$_label
;
const
NO_VALID_PARAMS
=
'no_valid_params_provided'
,
NOT_FOUND
=
'record_not_found'
,
NO_CREDENTIALS
=
'no_valid_authentication_information_provided'
,
INVALID_CREDENTIALS
=
'invalid_credentials'
,
MISSING_PARAM
=
'missing_param'
,
CANNOT_WRITE_COVER
=
'error_writing_cover'
,
CANNOT_SAVE_RECORD
=
'error_saving_record'
,
PROVIDER_ERROR
=
'provider_error'
,
NO_ANSWER
=
'no_answer_from_gateway'
;
protected
$_code
,
$_label
;
public
static
function
noValidParams
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_BAD_REQUEST
,
static
::
NO_VALID_PARAMS
);
}
public
static
function
notFound
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_NOT_FOUND
,
static
::
NOT_FOUND
);
}
public
static
function
noCredentials
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_PROXY_AUTHENTICATION_REQUIRED
,
static
::
NO_CREDENTIALS
);
}
public
static
function
invalidCredentials
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_UNAUTHORIZED
,
static
::
INVALID_CREDENTIALS
);
}
public
static
function
missingParam
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_BAD_REQUEST
,
static
::
MISSING_PARAM
);
}
public
static
function
cannotWriteCover
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_INTERNAL_SERVER_ERROR
,
static
::
CANNOT_WRITE_COVER
);
}
public
static
function
cannotSaveRecord
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_INTERNAL_SERVER_ERROR
,
static
::
CANNOT_SAVE_RECORD
);
}
public
static
function
providerError
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_BAD_GATEWAY
,
static
::
PROVIDER_ERROR
);
}
public
static
function
noAnswerFromGateway
()
{
return
new
static
(
StatusCodeInterface
::
STATUS_GATEWAY_TIMEOUT
,
static
::
NO_ANSWER
);
}
public
function
__construct
(
$code
,
$label
)
{
public
function
__construct
(
int
$code
,
string
$label
)
{
$this
->
_code
=
$code
;
$this
->
_label
=
$label
;
}
public
function
fetchRecord
()
{
return
(
new
FetchRecordResult
()
)
->
beError
(
$this
->
_code
,
$this
->
_label
);
public
function
fetchRecord
()
:
FetchRecordResult
{
return
(
new
FetchRecordResult
)
->
beError
(
$this
->
_code
,
$this
->
_label
);
}
}
src/Providers/FetchRecordResult.php
View file @
ccf136c0
...
...
@@ -15,9 +15,8 @@ class FetchRecordResult {
}
public
function
beSuccess
(
$record
){
public
function
beSuccess
(
$record
)
{
$this
->
_record
=
$record
;
$this
->
_record
->
save
();
$this
->
_status_code
=
200
;
return
$this
;
}
...
...
@@ -30,7 +29,7 @@ class FetchRecordResult {
}
public
function
getStatusCode
(){
public
function
getStatusCode
()
{
return
$this
->
_status_code
;
}
}
\ No newline at end of file
src/Providers/Local.php
View file @
ccf136c0
...
...
@@ -10,7 +10,7 @@ class Local extends Provider {
}
public
function
fetchRecord
()
{
return
(
new
FetchRecordResult
()
)
->
beSuccess
(
$this
->
_record
);
public
function
fetchRecord
()
:
FetchRecordResult
{
return
(
new
FetchRecordResult
)
->
beSuccess
(
$this
->
_record
);
}
}
src/Providers/Orb.php
View file @
ccf136c0
<?php
namespace
Pellicule\Providers
;
use
\
Pellicule\Models\Record
;
use
\
Fig\Http\Message\StatusCodeInterface
;
use
\
Pellicule\Models\Record
;
use
\
Pellicule\Models\Media
;
use
\
Pellicule\FileSystem
;
class
Orb
extends
Provider
{
class
Orb
extends
Remote
Provider
{
const
END_POINT
=
'https://api.base-orb.fr'
,
ID
=
'orb'
;
ID
=
'orb'
,
API_URL
=
'https://api.base-orb.fr/v1/products?sort=ean_asc&eans={ean}'
,
USERNAME_KEY
=
'username'
,
SECRET_KEY
=
'secret'
;
protected
$_username
,
$_secret
;
public
function
__construct
(
$data
=
[])
{
if
(
!
array_key_exists
(
'username'
,
$data
))
return
$this
;
$this
->
_username
=
$data
[
'username'
];
if
(
!
array_key_exists
(
'secret'
,
$data
))
return
$this
;
$this
->
_secret
=
$data
[
'secret'
];
public
function
__construct
(
array
$credentials
,
array
$args
)
{
parent
::
__construct
(
$credentials
,
$args
);
$this
->
_username
=
$credentials
[
static
::
USERNAME_KEY
]
??
''
;
$this
->
_secret
=
$credentials
[
static
::
SECRET_KEY
]
??
''
;
}
public
function
providerName
()
{
public
function
providerName
()
:
string
{
return
'Orb'
;
}
public
function
fetchRecord
()
{
$browser
=
static
::
newHttpClient
();
$ean
=
$this
->
_getIsbnOrEan
();
try
{
$response
=
$browser
->
get
(
static
::
END_POINT
.
'/v1/products?sort=ean_asc&eans='
.
$ean
,
[
'Authorization'
=>
'Basic '
.
base64_encode
(
$this
->
_username
.
':'
.
$this
->
_secret
),
'Accept-Encoding'
=>
'deflate, gzip'
]);
}
catch
(
\
Exception
$exception
)
{
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_GATEWAY_TIMEOUT
,
'no_answer_from_gateway'
);
}
if
(
$response
->
getStatusCode
()
!=
StatusCodeInterface
::
STATUS_OK
)
return
$this
->
_newError
(
$response
->
getStatusCode
(),
$response
->
getReasonPhrase
());
$response
->
getBody
()
->
rewind
();
$json_content
=
json_decode
(
zlib_decode
(
$response
->
getBody
()
->
getContents
()),
TRUE
);
if
(
!
$json_content
)
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_UNPROCESSABLE_ENTITY
,
'cannot_parse_response'
);
if
(
!
isset
(
$json_content
[
'data'
]))
return
$this
->
_newError
(
StatusCodeInterface
::
STATUS_NOT_ACCEPTABLE
,
'no_expected_attribute_in_data'
);
protected
function
_isValidCredentials
()
:
bool
{
return
$this
->
_username
&&
$this
->
_secret
;
}
$images
=
$json_content
[
'data'
][
0
][
'images'
];
$medialist
=
[];
$imagetype_translate
=
[
'front'
=>
'cover'
,
'back '
=>
'back_cover'
,
'additional'
=>
'extract'
];
foreach
(
array_keys
(
$images
)
as
$image_type
)
{
$medialist
[]
=
(
new
\
Pellicule\Models\Media
())
->
setProvider
(
$this
->
providerName
())
->
setType
(
$imagetype_translate
[
$image_type
])
->
setUrl
(
$images
[
$image_type
][
'original'
][
'src'
])
->
setFullsizeFromIdentifier
(
$this
->
_getIsbnOrEan
());
}
protected
function
_coverUrl
(
string
$identifier
)
{
$api_url
=
str_replace
(
'{ean}'
,
$identifier
,
static
::
API_URL
);
$headers
=
[
'Authorization'
=>
'Basic '
.
base64_encode
(
$this
->
_username
.
':'
.
$this
->
_secret
),
'Accept-Encoding'
=>
'deflate, gzip'
];
$re
cord
=
(
new
Record
(
))
->
updateAttributes
(
$this
->
_search_args
)
->
setMedia
(
$medialist
)
;
$re
sponse
=
$this
->
_withHttpDo
(
fn
(
$http
)
=>
$http
->
get
(
$api_url
,
$headers
))
;
if
(
$response
instanceof
FetchRecordResult
)
return
$response
;
return
(
new
FetchRecordResult
())
->
beSuccess
(
$record
);
return
((
$json_content
=
json_decode
(
zlib_decode
((
string
)
$response
->
getBody
()),
true
))
&&
(
$images
=
$json_content
[
'data'
][
0
][
'images'
]
??
[])
&&
(
$cover_url
=
$images
[
'front'
][
'original'
][
'src'
]
??
false
))
?
$cover_url
:
$this
->
_providerError
();
}
}
src/Providers/Provider.php
View file @
ccf136c0
...
...
@@ -13,32 +13,21 @@ abstract class Provider {
const
ID
=
''
;
protected
$_search_args
=
[],
$_headers
=
[];
public
static
function
handles
(
string
$provider_id
)
:
bool
{
return
static
::
ID
===
$provider_id
;
}
public
static
function
newProvider
(
$request
,
$args
)
{
return
static
::
_realNewProvider
(
$request
,
$args
)
->
setSearchArgs
(
$args
);
return
static
::
_realNewProvider
(
$request
,
$args
);
}
protected
static
function
_realNewProvider
(
$request
,
$args
)
{
if
(
!
$args
)
return
new
Error
(
StatusCodeInterface
::
STATUS_BAD_REQUEST
,
'no valid params provided'
);
return
Error
::
noValidParams
();
if
(
$record
=
Record
::
findFirstBy
(
$args
))
return
new
Local
(
$record
);
if
(
!
$request
->
hasHeader
(
'Authorization'
))
return
new
Error
(
StatusCodeInterface
::
STATUS_NOT_FOUND
,
'record
not
f
ound
'
);
return
Error
::
not
F
ound
(
);
$credentials
=
array_filter
((
new
Collection
(
$request
->
getHeader
(
'Authorization'
)))
->
select
(
function
(
$header
)
...
...
@@ -52,65 +41,18 @@ abstract class Provider {
->
getArrayCopy
());
if
(
!
$credentials
)
return
new
Error
(
StatusCodeInterface
::
STATUS_PROXY_AUTHENTICATION_REQUIRED
,
'no_valid_authentication_information_provided'
);
return
Error
::
noCredentials
();
return
(
$provider
=
(
new
RemoteProviders
)
->
providerFor
(
array_shift
(
$credentials
)))
return
(
$provider
=
(
new
RemoteProviders
)
->
providerFor
(
array_shift
(
$credentials
)
,
$args
))
?
$provider
:
new
Error
(
StatusCodeInterface
::
STATUS_BAD_REQUEST
,
'no valid params provided'
);
}
protected
function
_parseJsonResponse
(
$response
){
$response
->
getBody
()
->
rewind
();
return
json_decode
(
$response
->
getBody
()
->
getContents
());
}
protected
function
_getIsbnOrEan
()
{
if
(
isset
(
$this
->
_search_args
[
'ean'
]))
return
$this
->
_search_args
[
'ean'
];
if
(
isset
(
$this
->
_search_args
[
'isbn'
]))
return
$this
->
_search_args
[
'isbn'
];
:
Error
::
noValidParams
();
}
public
function
providerName
()
{
public
function
providerName
()
:
string
{
return
static
::
class
;
}
public
function
setSearchArgs
(
$args
)
{
if
(
isset
(
$args
[
'isbn'
])
||
isset
(
$args
[
'ean'
]))
$this
->
_search_args
=
$args
;
return
$this
;
}
public
function
setHeaders
(
$value
)
{
$this
->
_headers
=
$value
;
}
public
function
getHeaders
()
{
return
$this
->
_headers
;
}
/**
* @param int $status StatusCodeInterface::STATUS_* constant
* @param string $code
*
* @return FetchRecordResult
*/
public
function
_newError
(
$status
,
$code
)
{
return
(
new
FetchRecordResult
)
->
beError
(
$status
,
$code
);
}
/** @return FetchRecordResult */
abstract
public
function
fetchRecord
();
abstract
public
function
fetchRecord
()
:
FetchRecordResult
;
}
\ No newline at end of file
src/Providers/RemoteProvider.php
0 → 100644
View file @
ccf136c0
<?php
namespace
Pellicule\Providers
;
use
\
Fig\Http\Message\StatusCodeInterface
;
use
\
Pellicule\Models\Record
;
use
\
Pellicule\Models\Media
;
use
\
Pellicule\FileSystem
;
abstract
class
RemoteProvider
extends
Provider
{
protected
$_search_args
=
[];
public
static
function
handles
(
string
$provider_id
)
:
bool
{
return
static
::
ID
===
$provider_id
;
}
public
function
__construct
(
array
$credentials
,
array
$args
)
{
if
(
isset
(
$args
[
'isbn'
])
||
isset
(
$args
[
'ean'
]))
$this
->
_search_args
=
$args
;
}
public
function
fetchRecord
()
:
FetchRecordResult
{
if
(
!
$this
->
_isValidCredentials
())
return
$this
->
_invalidCredentials
();
if
(
!
$identifier
=
$this
->
_getIsbnOrEan
())
return
$this
->
_missingParam
();
$cover_url
=
$this
->
_coverUrl
(
$identifier
);
if
(
$cover_url
instanceof
FetchRecordResult
)
return
$cover_url
;
$response
=
$this
->
_withHttpDo
(
fn
(
$http
)
=>
$http
->
get
(
$cover_url
));
if
(
$response
instanceof
FetchRecordResult
)
return
$response
;
$media
=
Media
::
newInstance
([
'provider'
=>
$this
->
providerName
(),
'type'
=>
Media
::
TYPE_COVER
,
'url'
=>
$cover_url
,
'fullsize_from_identifier'
=>
$identifier
,
]);
if
(
!
(
new
FileSystem
)
->
writeMediaFrom
(
$media
,
$response
))
return
$this
->
_cannotWriteCover
();
$record
=
Record
::
newInstance
(
array_merge
(
$this
->
_search_args
,
[
'media'
=>
[
$media
]]));
return
$record
->
save
()
?
(
new
FetchRecordResult
)
->
beSuccess
(
$record
)
:
$this
->
_cannotSaveRecord
();
}
abstract
protected
function
_isValidCredentials
()
:
bool
;
abstract
protected
function
_coverUrl
(
string
$identifier
);
protected
function
_getIsbnOrEan
()
{
if
(
isset
(
$this
->
_search_args
[
'ean'
]))
return
$this
->
_search_args
[
'ean'
];
return
$this
->
_search_args
[
'isbn'
]
??
null
;
}