Commit a5870703 authored by srees's avatar srees
Browse files

Modify for best practices: SOLID, namespacing, autoloading

parent 8ca964b1
# ZenodoPHP # ZenodoPHP
A PHP library for utilizing the Zenodo API. Requires >= PHP 7.0. A PHP library for utilizing the Zenodo API. Requires >= PHP 7.1.
Zenodo is part of CERN and provides permanent archive and Digital Object Identifier (DOI) capabilities for free. You can sign up separately for their production environment at https://zenodo.org and for their sandbox environment at https://sandbox.zenodo.org Zenodo is part of CERN and provides permanent archive and Digital Object Identifier (DOI) capabilities (through DataCite) for free. You can sign up separately for their production environment at https://zenodo.org and for their sandbox environment at https://sandbox.zenodo.org
You will need to have an account with Zenodo to utilize this library for their REST API, which is documented at https://developers.zenodo.org. This library follow the documentation posted as of April 2020, with the exception of a couple minor details that Zenodo had not yet updated their documentation with. You will need to have an account with Zenodo to utilize this library for their REST API, which is documented at https://developers.zenodo.org. This library follows the documentation posted as of April 2020, with the exception of a couple minor details that Zenodo had not yet updated their documentation with.
This library was developed by Stephen Rees for the Cyber-Physical Systems Virtual Organization (https://cps-vo.org), Institute for Software Integrated Systems (https://www.isis.vanderbilt.edu), Vanderbilt University (https://vanderbilt.edu) under National Science Foundation award number 1521617 (https://nsf.gov/awardsearch/showAward?AWD_ID=1521617).
Licensing is under review at this time. The intention is to open-source this library, and the library will be updated with licensing information when review is complete.
# Example 1 - Retrieve all existing depositions # Example 1 - Retrieve all existing depositions
```php ```php
include_once('zenodoAPI.php'); require_once(__DIR__ . '/includes/zenodoAPI.php');
$sandboxtoken = '';//your sandbox token
$connection = new zenodoConnection($sandboxtoken, 'sandbox'); use zenodoAPI\zenodoConnection;
use zenodoAPI\zenodoDepositionCollection;
$sandboxtoken = '';//your sandbox token
$connection = new zenodoConnection($sandboxtoken, 'sandbox');
$collection_obj = new ZenodoDepositionCollection($connection); $collection_obj = new ZenodoDepositionCollection($connection);
try{ try{
$collection = $collection_obj->list_depositions();//returns an array of deposition objects $collection = $collection_obj->list_depositions();//returns an array of deposition objects
}catch (RuntimeException $e){ }
catch (RuntimeException $e) {
//you should only have to catch the runtime exception the first time you request data from Zenodo //you should only have to catch the runtime exception the first time you request data from Zenodo
echo $e->getMessage(); echo $e->getMessage();
echo var_export($connection->last_response());//this is where you will find details on any errors echo var_export($connection->last_response());//this is where you will find details on any errors
return; return;
}catch(LogicException $e){ }
catch (LogicException $e) {
//you should catch a logic exception every time you request data from Zenodo. //you should catch a logic exception every time you request data from Zenodo.
echo $e->getMessage(); echo $e->getMessage();
echo var_export($connection->last_response()); echo var_export($connection->last_response());
...@@ -32,7 +39,12 @@ echo var_export($collection); ...@@ -32,7 +39,12 @@ echo var_export($collection);
# Example 2 - Retrieve a specific deposition # Example 2 - Retrieve a specific deposition
```php ```php
//leaving off the exception handling this time //leaving off the exception handling this time
require_once(__DIR__ . '/includes/zenodoAPI.php');
use zenodoAPI\zenodoConnection;
use zenodoAPI\zenodoDeposition;
$sandboxtoken = '';//your sandbox token
$connection = new zenodoConnection('your_sandbox_token', 'sandbox'); $connection = new zenodoConnection('your_sandbox_token', 'sandbox');
$deposition = new zenodoDeposition($connection); $deposition = new zenodoDeposition($connection);
$deposition->retrieve_deposition(12345);//whatever your deposition ID is... $deposition->retrieve_deposition(12345);//whatever your deposition ID is...
...@@ -40,6 +52,12 @@ echo var_export($deposition->clean());//clean returns a cloned object stripped o ...@@ -40,6 +52,12 @@ echo var_export($deposition->clean());//clean returns a cloned object stripped o
``` ```
# Example 3 - Create a deposition # Example 3 - Create a deposition
```php ```php
require_once(__DIR__ . '/includes/zenodoAPI.php');
use zenodoAPI\zenodoConnection;
use zenodoAPI\zenodoDeposition;
$sandboxtoken = '';//your sandbox token
$connection = new zenodoConnection('your_sandbox_token', 'sandbox'); $connection = new zenodoConnection('your_sandbox_token', 'sandbox');
$deposition = new zenodoDeposition($connection); $deposition = new zenodoDeposition($connection);
$deposition->create_deposition();//deposition object is now created with Zenodo and ready to be populated with metadata $deposition->create_deposition();//deposition object is now created with Zenodo and ready to be populated with metadata
...@@ -56,10 +74,10 @@ $deposition->metadata->publication_date(date('Y-m-d')); ...@@ -56,10 +74,10 @@ $deposition->metadata->publication_date(date('Y-m-d'));
$deposition->metadata->title('My First Deposition'); $deposition->metadata->title('My First Deposition');
$creator = new stdClass(); $creator = new stdClass();
$creator->name = 'Family name, Given names'; $creator->name = 'Family name, Given names';
$deposition->metadata->creators(array($creator)); $deposition->metadata->creators([$creator]);
$deposition->metadata->description('This is an example of how to create a deposition with the Zenodo API'); $deposition->metadata->description('This is an example of how to create a deposition with the Zenodo API');
$deposition->metadata->access_right('closed'); $deposition->metadata->access_right('closed');
$deposition->metadata->keywords(array('first','example')); $deposition->metadata->keywords(['first', 'example']);
$deposition->update_deposition(); $deposition->update_deposition();
//Want to publish it? This locks it against changes and deletion permanently //Want to publish it? This locks it against changes and deletion permanently
......
This diff is collapsed.
<?php
namespace zenodoAPI;
class zenodoConnection
{
const sandBoxURL = 'https://sandbox.zenodo.org/api/';
const apiURL = 'https://zenodo.org/api/';
private $connect_URL;
private $access_token;
private $last_response;
public function __construct(string $_token, string $_target = '')
{
if (empty($_token)) {
throw new \InvalidArgumentException(
'Token for zenodoConnection cannot be empty'
);
}
if ($_target == 'production') {//this should be exposed as a config setting in Drupal that can be additionally controlled via settings.php
$this->connect_URL = self::apiURL;
} else {
$this->connect_URL = self::sandBoxURL;
}
$this->access_token = $_token;
$this->last_response = [];
}
public function last_response()
{
return $this->last_response;
}
public function make_request(
$_verb = 'GET',
$_path = '',
$_content_type = '',
$_data = ''
) {
$ch = curl_init($this->connect_URL . $_path);
$options = [
CURLOPT_CUSTOMREQUEST => $_verb,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->access_token,
],
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_POSTREDIR => 3,
];
if (!empty($_content_type)) {
$options[CURLOPT_HTTPHEADER][] = "Content-Type: $_content_type";
}
if (!empty($_data)) {
if ($_content_type == 'application/json') {
$_data = json_encode($_data);
}
if ($_content_type == 'multipart/form-data') {
$file = curl_file_create(
realpath($_data),
null,
basename($_data)
);//TODO: do we want to include mimetype as part of this?
$_data = ['file' => $file];
}
$options[CURLOPT_POSTFIELDS] = $_data;
}
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
$curl_info = curl_getinfo($ch);
$curl_error = curl_error($ch);
curl_close($ch);
$this->last_response = [
'data' => $_data,
'response' => $response,
'curl_info' => $curl_info,
'curl_error' => $curl_error,
];
if ($response === false) {//address curl failure
throw new \RuntimeException(
'Curl Failed. Check last_response() for details.'
);
}
$response_data = json_decode($response);
//responses, including errors, are returned as jSON objects -- with the exception of collections, which are returned as an array of objects
if (is_object(
$response_data
) && isset($response_data->status) && (int)$response_data->status > 204) {
throw new \LogicException(
'Request failed. Check last_response() for details.'
);
}
return $response_data;
}
}
\ No newline at end of file
<?php
namespace zenodoAPI;
class zenodoDeposition
{
private $connection;
private $created;
private $doi;
private $doi_url;
private $files;
private $id;
private $metadata;
private $modified;
private $owner;
private $record_id;
private $record_url;
private $state;
private $submitted;
private $title;
private $conceptrecid;
private $links;
public function __construct(zenodoConnection $_connection)
{
$this->connection = $_connection;
$this->created = 0;//timestamp
$this->doi = '';
$this->doi_url = '';
$this->files = [];
$this->id = 0;
$this->metadata = new zenodoDepositionMetadata();
$this->modified = 0;//timestamp
$this->owner = 0;
$this->record_id = 0;
$this->record_url = '';
$this->state = '';
$this->submitted = false;
$this->title = '';
$this->conceptrecid = 0;
$this->links = new \stdClass();
}
/*
* Provides a way to load the entire object from a json decoded result
* while still retaining custom getters and setters
*/
public function load(\stdClass $_jsonObject)
{
//the ID field needs to be loaded first for child objects to load properly
$this->id($_jsonObject->id);
foreach ($_jsonObject as $key => $value) {
if (method_exists($this, $key)) {
$this->$key($value);
}
}
}
public function __get(string $_name)
{
if (isset($this->$_name)) {
return $this->$_name;
} else {
throw new \InvalidArgumentException(
'Attempted to get non-existent value from object'
);
}
}
/***************************
* Manual setters to address custom validation/access
*************************/
/**
* @param string $_setValue
*/
private function created(string $_setValue)
{
$this->created = $_setValue;
}
private function doi(string $_setValue)
{
$this->doi = $_setValue;
}
private function doi_url(string $_setValue)
{
if (filter_var($_setValue, FILTER_VALIDATE_URL)) {
$this->doi_url = $_setValue;
} else {
throw new \InvalidArgumentException(
'DOI URL failed to validate as a URL'
);
}
}
public function files(array $_setValue)
{
$new_files = [];
foreach ($_setValue as $file) {
if (is_object($file) && $file instanceof zenodoDepositionFile) {
$new_files[] = $file;
} elseif (is_object($file) && $file instanceof \stdClass) {
$create = new zenodoDepositionFile(
$this->connection, $this->id
);
$create->load($file);
$new_files[] = $create;
} else {
throw new \InvalidArgumentException(
'Attempted to set files with invalid file object in array'
);
}
}
$this->files = $new_files;
}
public function id(int $_setValue)
{
if ($this->id == 0 || $this->id == $_setValue) {
$this->id = $_setValue;
} else {
throw new \LogicException(
'Cannot change the ID of an already loaded deposition'
);
}
}
public function metadata($_setValue)
{
if (is_object(
$_setValue
) && $_setValue instanceof zenodoDepositionMetadata) {
$this->metadata = $_setValue;
} elseif (is_object($_setValue) && $_setValue instanceof \stdClass) {
$this->metadata = new zenodoDepositionMetadata();
$this->metadata->load($_setValue);
} else {
throw new \InvalidArgumentException(
'Attempted to set metadata with invalid object'
);
}
}
private function modified(string $_setValue)
{
$this->modified = $_setValue;
}
private function owner(int $_setValue)
{
$this->owner = $_setValue;
}
private function record_id(int $_setValue)
{
$this->record_id = $_setValue;
}
private function record_url(string $_setValue)
{
if (filter_var($_setValue, FILTER_VALIDATE_URL)) {
$this->record_url = $_setValue;
} else {
throw new \InvalidArgumentException(
'Record URL failed to validate as a URL'
);
}
}
private function state(string $_setValue)
{
$allowed_states = ['unsubmitted', 'inprogress', 'done', 'error'];
if (in_array($_setValue, $allowed_states)) {
$this->state = $_setValue;
} else {
throw new \InvalidArgumentException(
'Invalid state option provided'
);
}
}
private function submitted(bool $_setValue)
{
$this->submitted = $_setValue;
}
private function title(string $_setValue)
{
$this->title = $_setValue;
}
private function conceptrecid(int $_setValue)
{
$this->conceptrecid = $_setValue;
}
private function links(\stdClass $_setValue)
{
foreach ($_setValue as $name => $value) {
$this->links->$name = $value;
}
}
public function clean(): \stdClass
{
$clean_obj = new \stdClass();
foreach (get_object_vars($this) as $name => $value) {
if ($name == 'connection') {
continue;
} elseif ($name == 'files') {
$clean_obj->files = [];
foreach ($value as $file) {
$clean_obj->files[] = $file->clean();
}
} elseif ($name == 'metadata') {
$clean_obj->metadata = $value->clean();
} else {
$clean_obj->$name = $value;
}
}
return $clean_obj;
}
/*****************
* API functions
****************/
//Can accept either an empty object or a Deposition Metadata Resource
/**
* @param null $_deposition
*
* @throws \InvalidArgumentException
*/
public function create_deposition($_deposition = null)
{
if (is_null(
$_deposition
) || $_deposition instanceof zenodoDepositionMetadata) {
if (is_null($_deposition)) {
$deposition = new \stdClass();
} else {
$deposition = ['metadata' => $_deposition->clean()];
}
//errors in the request will generate an exception that needs to be handled by code implementing this library
$this->load(
$this->connection->make_request(
'POST',
'deposit/depositions',
'application/json',
$deposition
)
);
} else {
throw new \InvalidArgumentException(
'Create deposition requires an empty object, NULL, or a zenodoDepositionMetaData object'
);
}
}
public function retrieve_deposition(int $_id)
{
//errors in the request will generate an exception that needs to be handled by code implementing this library
$this->load(
$this->connection->make_request('GET', 'deposit/depositions/' . $_id)
);
}
/*
* This will wipe out this object and replace it with the response!
* It is passed only the metadata object.
*/
public function update_deposition()
{
//errors in the request will generate an exception that needs to be handled by code implementing this library
$this->load(
$this->connection->make_request(
'PUT',
"deposit/depositions/$this->id",
'application/json',
['metadata' => $this->metadata->clean()]
)
);
}
public function delete_deposition()
{
//can only delete a deposition that is not yet published
$this->connection->make_request(
'DELETE',
"deposit/depositions/$this->id"
);
}
public function list_files()
{
$this->files(
$this->connection->make_request(
'GET',
"deposit/depositions/$this->id/files"
)
);
}
public function add_file(string $_file): zenodoDepositionFile
{
if (file_exists(realpath($_file))) {
$newfile = new zenodoDepositionFile($this->connection, $this->id);
$newfile->create_file($_file);
$this->files[] = $newfile;
return $newfile;
} else {
throw new \InvalidArgumentException('File cannot be found');
}
}
public function sort_files()
{
//it may be better to just accept an array of integers and validate them against the files we have
//first file is shown in file preview. We should pass an array of deposition file resources, each with only the id attribute.
$file_array = [];
foreach ($this->files as $file) {
$file_object = new \stdClass();
$file_object->id = $file->id();
$file_array[] = $file_object;
}
$this->files(
$this->connection->make_request(
'PUT',
"deposit/depositions/$this->id/files",
'application/json',
$file_array
)
);
}
public function publish_deposition()
{
$this->load(
$this->connection->make_request(
'POST',
"deposit/depositions/$this->id/actions/publish"
)
);
}
public function edit_deposition()
{
$this->load(
$this->connection->make_request(
'POST',
"deposit/depositions/$this->id/actions/edit"
)
);
}
//Alias for edit_deposition
public function unlock_deposition()
{
$this->edit_deposition();
}
public function discard_deposition()
{
$this->load(
$this->connection->make_request(
'POST',
"deposit/depositions/$this->id/actions/discard"
)
);
}
//Alias for discard_deposition
public function reset_deposition()
{
$this->discard_deposition();
}
public function clone_deposition()
{
//This returns the original deposition! The new, unpublished version can be access through the 'latest_draft' under 'links' in the response.
$this->load(
$this->connection->make_request(
'POST',
"deposit/depositions/$this->id/actions/newversion"
)
);
}
//Alias for clone
public function new_version_deposition()
{
$this->clone_deposition();
}
}
\ No newline at end of file