diff --git a/.github/workflows/citests.yml b/.github/workflows/citests.yml index 26fd37f..93ad7f5 100644 --- a/.github/workflows/citests.yml +++ b/.github/workflows/citests.yml @@ -18,7 +18,9 @@ jobs: extensions: xml, curl, dom, mbstring - name: Install Composer dependencies run: composer install - - name: Running unit test - run: ./vendor/bin/phpunit Tests + - name: Running unit tests + run: ./vendor/bin/phpunit Tests/Unit + - name: Running integration tests + run: ./vendor/bin/phpunit Tests/Int env: API_KEY: ${{ secrets.API_KEY }} diff --git a/Payload/API.php b/Payload/API.php index 18cfca0..15feebc 100644 --- a/Payload/API.php +++ b/Payload/API.php @@ -1,15 +1,17 @@ diff --git a/Payload/ARMObject.php b/Payload/ARMObject.php index 59313f3..99411bd 100644 --- a/Payload/ARMObject.php +++ b/Payload/ARMObject.php @@ -3,14 +3,40 @@ require_once('ARMRequest.php'); -class ARMObject { - private static $_object_cache = array(); - public static $spec = array(); - public static $type = null; +class ARMObject +{ + private static $_object_cache = []; + private static $_registry = []; + public static $spec = []; + private static $_default_params = []; + + public static function setDefaultParams($params) { + self::$_default_params[get_called_class()] = $params; + } + + public static function getDefaultParams() { + return self::$_default_params[get_called_class()] ?? []; + } + + public static function getRegistry() + { + if (empty(self::$_registry)) { + foreach (glob(__DIR__ . '/*.php') as $file) { + require_once($file); + } + foreach (get_declared_classes() as $class) { + if (is_subclass_of($class, self::class) && isset($class::$spec['object'])) { + self::$_registry[$class::$spec['object']] = $class; + } + } + } + return self::$_registry; + } - public static function new($data) { + public static function new($data) + { $class = get_called_class(); - if ( isset($data['id']) && isset(self::$_object_cache[$data['id']]) ) { + if (isset($data['id']) && isset(self::$_object_cache[$data['id']])) { $cached_object = self::$_object_cache[$data['id']]; $cached_object->data($data); return $cached_object; @@ -18,80 +44,129 @@ public static function new($data) { return new $class($data); } - function __construct($data) { - if ($data !== null) + function __construct($data) + { + if ($data !== null) { $this->data($data); + } + } + + private static function _build_request($cls = null, $apply_polymorphic = false) + { + $cls = $cls ?: get_called_class(); + $req = new ARMRequest($cls); + if ($apply_polymorphic && isset($cls::$spec['polymorphic_type'])) { + $req->filter_by(['type' => $cls::$spec['polymorphic_type']]); + } + return $req; } - public function __get($name) { - if (array_key_exists($name, $this->_data)) + public function __get($name) + { + if (array_key_exists($name, $this->_data)) { return $this->_data[$name]; - else + } else { throw new \Exception("$name does not exist"); + } } - public function json() { + public function json() + { return json_encode($this->data(), JSON_PRETTY_PRINT); } - public function data($data=null) { - if ( $data !== null ) { + public function data($data = null) + { + if ($data !== null) { $this->_data = Utils::data2object($data); - if ( isset($data['id']) ) + if (isset($data['id'])) { self::$_object_cache[$this->id] = $this; + } } else { return Utils::object2data($this->_data); } } - public function update($update) { - return (new ARMRequest(get_called_class()))->request('put', - array('id'=>$this->id, 'json'=>$update)); + public function update($update) + { + return static::_build_request(static::class)->request( + 'put', + ['id'=>$this->id, 'json'=>$update] + ); } - public function delete($update=null) { - if ($update !== null){ - return (new ARMRequest(get_called_class()))->request('delete', - array('id'=>$update->id)); + public function delete($update = null) + { + if ($update !== null) { + return static::_build_request(static::class)->request( + 'delete', + ['id'=>$update->id] + ); } else { - return (new ARMRequest(get_called_class()))->request('delete', - array('id'=>$this->id)); + return static::_build_request(static::class)->request( + 'delete', + ['id'=>$this->id] + ); } } - public static function get($id) { - return (new ARMRequest(get_called_class()))->get($id); + public static function get($id) + { + return static::_build_request()->get($id); } - public static function filter_by(...$filters) { - if ( isset(get_called_class()::$spec['polymorphic_type']) ) - array_push($filters, ['type' => get_called_class()::$spec['polymorphic_type']]); + public static function all() + { + return static::_build_request()->all(); + } - $req = new ARMRequest(get_called_class()); - return call_user_func_array(array($req, 'filter_by'), $filters); + public static function filter_by(...$filters) + { + $req = static::_build_request(null, true); + return call_user_func_array([$req, 'filter_by'], $filters); } - public static function create($obj) { - if ( isset(get_called_class()::$spec['polymorphic_type']) ) + public static function create($obj) + { + if (isset(get_called_class()::$spec['polymorphic_type'])) { $obj['type'] = get_called_class()::$spec['polymorphic_type']; + } - return (new ARMRequest(get_called_class()))->create($obj); + return static::_build_request()->create($obj); } - public static function select(...$attrs) { - $req = new ARMRequest(get_called_class()); - return call_user_func_array(array($req, 'select'), $attrs); + public static function select(...$attrs) + { + $req = static::_build_request(null, true); + return call_user_func_array([$req, 'select'], $attrs); } - public static function delete_all(...$objs) { + public static function order_by(...$attrs) + { + $req = static::_build_request(null, true); + return call_user_func_array([$req, 'order_by'], $attrs); + } + + public static function limit($limit) + { + return static::_build_request(null, true)->limit($limit); + } + + public static function offset($offset) + { + return static::_build_request(null, true)->offset($offset); + } - $func = function($value) { + public static function delete_all(...$objs) + { + $func = function ($value) { return $value->id; }; - return (new ARMRequest(get_called_class()))->request('delete', - array('id'=>join("|",array_map($func, $objs)))); + return static::_build_request()->request( + 'delete', + ['id'=>join("|", array_map($func, $objs))] + ); } } -?> diff --git a/Payload/ARMRequest.php b/Payload/ARMRequest.php index 54c2904..70dea7d 100644 --- a/Payload/ARMRequest.php +++ b/Payload/ARMRequest.php @@ -1,145 +1,224 @@ cls = $cls; - $this->filters = array(); - $this->attrs = array(); - $this->group_by = array(); } - function request($method, $args=array()) { - if ( isset($this->cls::$spec['endpoint']) ) + function request($method, $args = []) + { + if (isset($this->cls::$spec['endpoint'])) { $endpoint = $this->cls::$spec['endpoint']; - else - $endpoint = '/'.$this->cls::$spec['object'] . 's'; + } else { + $object = $this->cls::$spec['object']; + $endpoint = '/'.$object . (substr($object, -1) === 's' ? '' : 's'); + } - if (isset($args['id'])) + if (isset($args['id'])) { $endpoint .= '/'.$args['id']; + } - $params = array_merge(array(), $this->filters); + $params = array_merge([], $this->filters); - if (count($this->attrs)) + if (count($this->attrs)) { $params['fields'] = array_map(function ($item) { return strval($item); }, $this->attrs); + } - if (count($this->group_by)) - $params['fields'] = array_map(function ($item) { + if (count($this->group_by)) { + $params['group_by'] = array_map(function ($item) { return strval($item); }, $this->group_by); + } + + if (count($this->order_by)) { + $params['order_by'] = array_map(function ($item) { + return strval($item); + }, $this->order_by); + } - if (isset($args['mode'])) + if (isset($this->limit)) { + $params['limit'] = $this->limit; + } + + if (isset($this->offset)) { + $params['offset'] = $this->offset; + } + + if (isset($args['mode'])) { $params['mode'] = $args['mode']; + } - if (count($params)) + $defaults = $this->cls::getDefaultParams(); + if ($defaults) { + foreach ($defaults as $k => $v) { + if (!isset($params[$k])) { + $params[$k] = $v; + } + } + } + + if (count($params)) { $endpoint .= '?'.http_build_query($params, '', '&'); + } $req = curl_init(API::$api_url . $endpoint); curl_setopt($req, CURLOPT_CUSTOMREQUEST, strtoupper($method)); curl_setopt($req, CURLOPT_USERPWD, API::$api_key . ':'); curl_setopt($req, CURLOPT_RETURNTRANSFER, true); - $headers = array(); + $headers = []; if (isset($args['json'])) { - $json = json_encode($args['json']); - curl_setopt($req, CURLOPT_POSTFIELDS, $json); - $headers[] = 'Content-Type: application/json'; - $headers[] = 'Content-Length: '.strlen($json); + $json = json_encode($args['json']); + curl_setopt($req, CURLOPT_POSTFIELDS, $json); + $headers[] = 'Content-Type: application/json'; + $headers[] = 'Content-Length: '.strlen($json); } if (isset(API::$api_version)) { - $headers[] = 'X-API-Version: '.API::$api_version; + $headers[] = 'X-API-Version: '.API::$api_version; } if (count($headers) > 0) { - curl_setopt($req, CURLOPT_HTTPHEADER, $headers); + curl_setopt($req, CURLOPT_HTTPHEADER, $headers); } - $resp = curl_exec($req); + $resp = curl_exec($req); $status_code = curl_getinfo($req, CURLINFO_HTTP_CODE); curl_close($req); $result = json_decode($resp, true); - if ( $result === null ) { - if ( $status_code == 500 ) throw new Exceptions\InternalServerError(); + if ($result === null) { + if ($status_code == 500) { + throw new Exceptions\InternalServerError(); + } throw new Exceptions\UnknownResponse(); } - if ( !isset($result['object']) ) + if (!isset($result['object'])) { return $result; + } - if ( $status_code == 200 ) { - if ( $result['object'] == 'list' ) - return array_map(function($item) { return $this->cls::new($item); }, + if ($status_code == 200) { + if ($result['object'] == 'list') { + return array_map(function ($item) { + return $this->cls::new($item); + }, $result['values']); + } return $this->cls::new($result); } - if ( $result['object'] == 'error' ) { - foreach( Utils::subclasses(Exceptions\PayloadError::class) as $exc ) { - if ( $exc::getClassName() != $result['error_type']) continue; - throw new $exc($result['error_description'], $result); + if ($result['object'] == 'error') { + foreach (get_declared_classes() as $cls) { + if (!is_subclass_of($cls, Exceptions\PayloadError::class)) { + continue; + } + if ($cls::getClassName() != $result['error_type']) { + continue; + } + throw new $cls($result['error_description'], $result); } throw new Exceptions\BadRequest($result['error_description'], $result); } throw new Exceptions\UnknownResponse(); - } - public function get($id) { - return self::request('GET', array('id'=>$id)); + public function get($id) + { + return self::request('GET', ['id'=>$id]); } - public function select(...$attrs) { + public function select(...$attrs) + { $this->attrs = array_merge($this->attrs, $attrs); return $this; } - public function group_by(...$attrs) { + public function group_by(...$attrs) + { $this->group_by = array_merge($this->group_by, $attrs); return $this; } - public function create($obj) { - if ( !Utils::is_assoc_array( $obj ) ) - $obj = array('object'=>'list', 'values'=>$obj); + public function order_by(...$attrs) + { + $this->order_by = array_merge($this->order_by, $attrs); + return $this; + } + + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + public function offset($offset) + { + $this->offset = $offset; + return $this; + } + + public function create($obj) + { + if (!Utils::is_assoc_array($obj)) { + $obj = ['object'=>'list', 'values'=>$obj]; + } $obj = Utils::object2data($obj); - return self::request('POST', array('json'=>$obj)); + return self::request('POST', ['json'=>$obj]); } - public function delete($obj) { - if ( !Utils::is_assoc_array( $obj ) ) - $obj = array('object'=>'list', 'values'=>$obj); + public function delete($obj) + { + if (!Utils::is_assoc_array($obj)) { + $obj = ['object'=>'list', 'values'=>$obj]; + } $obj = Utils::object2data($obj); - return self::request('DELETE', array('json'=>$obj, 'mode'=>'query')); + return self::request('DELETE', ['json'=>$obj, 'mode'=>'query']); } - public function update($obj) { - if ( !Utils::is_assoc_array( $obj ) ) - $obj = array('object'=>'list', 'values'=>$obj); - if ( $obj instanceof ARMObject ) + public function update($obj) + { + if (!Utils::is_assoc_array($obj)) { + $obj = ['object'=>'list', 'values'=>$obj]; + } + if ($obj instanceof ARMObject) { $obj = $obj->data(); - else + } else { $obj = Utils::object2data($obj); - return self::request('PUT', array('json'=>$obj, 'mode'=>'query')); + } + return self::request('PUT', ['json'=>$obj, 'mode'=>'query']); } - public function filter_by(...$filters) { - $filters = call_user_func_array('array_merge', $filters); - $this->filters = array_merge($filters, $this->filters); + public function filter_by(...$filters) + { + if (count($filters)) { + $filters = call_user_func_array('array_merge', $filters); + $this->filters = array_merge($filters, $this->filters); + } return $this; } - public function all() { + public function all() + { return $this->request('get'); } } - -?> diff --git a/Payload/AccessToken.php b/Payload/AccessToken.php index 2eed789..b037eea 100644 --- a/Payload/AccessToken.php +++ b/Payload/AccessToken.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class AccessToken extends ARMObject { - public static $spec = array('object'=>'access_token'); +class AccessToken extends ARMObject +{ + public static $spec = ['object'=>'access_token']; } -?> diff --git a/Payload/Account.php b/Payload/Account.php index 9db2d10..d6414de 100644 --- a/Payload/Account.php +++ b/Payload/Account.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Account extends ARMObject { - public static $spec = array('object'=>'account'); +class Account extends ARMObject +{ + public static $spec = ['object'=>'account']; } -?> diff --git a/Payload/Attr.php b/Payload/Attr.php index 9f8125d..525dfce 100644 --- a/Payload/Attr.php +++ b/Payload/Attr.php @@ -1,58 +1,74 @@ param = $param; $this->parent = $parent; - if ( !$this->parent || !$this->parent->key ) + if (!$this->parent || !$this->parent->key) { $this->key = $this->param; - else + } else { $this->key = $this->parent->key . '[' . $this->param . ']'; + } } - public function __get($key) { + public function __get($key) + { return new Attr($key, $this); } - public function __toString() { - if ($this->is_method) + public function __toString() + { + if ($this->is_method) { return $this->param . '(' . $this->parent->key . ')'; - return $this->key; + } + return $this->key ?? ''; } - public function eq($val) { - $ret = array(); + public function eq($val) + { + $ret = []; $ret[strval($this)] = $val; return $ret; } - public function ne($val) { + public function ne($val) + { return $this->eq('!'.$val); } - public function lt($val) { + public function lt($val) + { return $this->eq('<'.$val); } - public function le($val) { + public function le($val) + { return $this->eq('<='.$val); } - public function gt($val) { + public function gt($val) + { return $this->eq('>'.$val); } - public function ge($val) { + public function ge($val) + { return $this->eq('>='.$val); } - public function contains($val) { + public function contains($val) + { return $this->eq('?*'.$val.'*'); } -} -?> + public function in(...$vals) + { + return $this->eq(implode('|', $vals)); + } +} diff --git a/Payload/BillingCharge.php b/Payload/BillingCharge.php index 913a7c9..ac4204e 100644 --- a/Payload/BillingCharge.php +++ b/Payload/BillingCharge.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class BillingCharge extends ARMObject { - public static $spec = array('object'=>'billing_charge'); +class BillingCharge extends ARMObject +{ + public static $spec = ['object'=>'billing_charge']; } -?> diff --git a/Payload/BillingItem.php b/Payload/BillingItem.php index 688fb59..1c30dda 100644 --- a/Payload/BillingItem.php +++ b/Payload/BillingItem.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class BillingItem extends ARMObject { - public static $spec = array('object'=>'billing_item'); +class BillingItem extends ARMObject +{ + public static $spec = ['object'=>'billing_item']; } -?> diff --git a/Payload/BillingSchedule.php b/Payload/BillingSchedule.php index 1042b42..72a42e0 100644 --- a/Payload/BillingSchedule.php +++ b/Payload/BillingSchedule.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class BillingSchedule extends ARMObject { - public static $spec = array('object'=>'billing_schedule'); +class BillingSchedule extends ARMObject +{ + public static $spec = ['object'=>'billing_schedule']; } -?> diff --git a/Payload/CheckBack.php b/Payload/CheckBack.php index b06b9dc..5baf4ad 100644 --- a/Payload/CheckBack.php +++ b/Payload/CheckBack.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class CheckBack extends ARMObject { - public static $spec = array('object'=>'check_back'); +class CheckBack extends ARMObject +{ + public static $spec = ['object'=>'check_back']; } -?> diff --git a/Payload/CheckFront.php b/Payload/CheckFront.php index 053553c..7ef53cc 100644 --- a/Payload/CheckFront.php +++ b/Payload/CheckFront.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class CheckFront extends ARMObject { - public static $spec = array('object'=>'check_front'); +class CheckFront extends ARMObject +{ + public static $spec = ['object'=>'check_front']; } -?> diff --git a/Payload/ClientToken.php b/Payload/ClientToken.php index 257ed94..b411fd9 100644 --- a/Payload/ClientToken.php +++ b/Payload/ClientToken.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class ClientToken extends ARMObject { - public static $spec = array('object'=>'access_token', 'polymorphic_type'=>'client'); +class ClientToken extends ARMObject +{ + public static $spec = ['object'=>'access_token', 'polymorphic_type'=>'client']; } -?> diff --git a/Payload/Customer.php b/Payload/Customer.php index 8a2bc48..662506f 100644 --- a/Payload/Customer.php +++ b/Payload/Customer.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Customer extends ARMObject { - public static $spec = array('object'=>'customer'); +class Customer extends ARMObject +{ + public static $spec = ['object'=>'customer']; } -?> diff --git a/Payload/Entity.php b/Payload/Entity.php index c34d612..c9f4dfd 100644 --- a/Payload/Entity.php +++ b/Payload/Entity.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Entity extends ARMObject { - public static $spec = array('object'=>'entity'); +class Entity extends ARMObject +{ + public static $spec = ['object'=>'entity', 'endpoint'=>'/entities']; } -?> diff --git a/Payload/Exceptions.php b/Payload/Exceptions.php index c840782..053485d 100644 --- a/Payload/Exceptions.php +++ b/Payload/Exceptions.php @@ -10,4 +10,3 @@ require(dirname(__FILE__) . '/Exceptions/TooManyRequests.php'); require(dirname(__FILE__) . '/Exceptions/Unauthorized.php'); require(dirname(__FILE__) . '/Exceptions/UnknownResponse.php'); -?> diff --git a/Payload/Exceptions/BadRequest.php b/Payload/Exceptions/BadRequest.php index 3d102df..307cd2a 100644 --- a/Payload/Exceptions/BadRequest.php +++ b/Payload/Exceptions/BadRequest.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/InternalServerError.php b/Payload/Exceptions/InternalServerError.php index 13a364f..8328d0b 100644 --- a/Payload/Exceptions/InternalServerError.php +++ b/Payload/Exceptions/InternalServerError.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/InvalidAttributes.php b/Payload/Exceptions/InvalidAttributes.php index 4e42c13..651823e 100644 --- a/Payload/Exceptions/InvalidAttributes.php +++ b/Payload/Exceptions/InvalidAttributes.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/NotFound.php b/Payload/Exceptions/NotFound.php index d4d6480..407c3ee 100644 --- a/Payload/Exceptions/NotFound.php +++ b/Payload/Exceptions/NotFound.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/NotPermitted.php b/Payload/Exceptions/NotPermitted.php index fbfd0f5..67319e2 100644 --- a/Payload/Exceptions/NotPermitted.php +++ b/Payload/Exceptions/NotPermitted.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/PayloadError.php b/Payload/Exceptions/PayloadError.php index 7f08e67..9db4295 100644 --- a/Payload/Exceptions/PayloadError.php +++ b/Payload/Exceptions/PayloadError.php @@ -1,18 +1,20 @@ getShortName(); } - public function __construct($message=null, $error = null) { - if ( $error && array_key_exists("details", $error) ) + public function __construct($message = null, $error = null) + { + if ($error && array_key_exists("details", $error)) { $this->details = $error["details"]; + } return parent::__construct($message); - } } -?> diff --git a/Payload/Exceptions/ServiceUnavailable.php b/Payload/Exceptions/ServiceUnavailable.php index 105c9fe..6904cee 100644 --- a/Payload/Exceptions/ServiceUnavailable.php +++ b/Payload/Exceptions/ServiceUnavailable.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/TooManyRequests.php b/Payload/Exceptions/TooManyRequests.php index 71c891c..18fe86b 100644 --- a/Payload/Exceptions/TooManyRequests.php +++ b/Payload/Exceptions/TooManyRequests.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/TransactionDeclined.php b/Payload/Exceptions/TransactionDeclined.php index bb63e18..f2217e8 100644 --- a/Payload/Exceptions/TransactionDeclined.php +++ b/Payload/Exceptions/TransactionDeclined.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/Unauthorized.php b/Payload/Exceptions/Unauthorized.php index 5ae4762..6da7117 100644 --- a/Payload/Exceptions/Unauthorized.php +++ b/Payload/Exceptions/Unauthorized.php @@ -1,7 +1,7 @@ diff --git a/Payload/Exceptions/UnknownResponse.php b/Payload/Exceptions/UnknownResponse.php index bc8c14a..f0b2ddc 100644 --- a/Payload/Exceptions/UnknownResponse.php +++ b/Payload/Exceptions/UnknownResponse.php @@ -1,6 +1,6 @@ diff --git a/Payload/Intent.php b/Payload/Intent.php index 41d06e6..65306c1 100644 --- a/Payload/Intent.php +++ b/Payload/Intent.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Intent extends ARMObject { - public static $spec = array('object'=>'intent'); +class Intent extends ARMObject +{ + public static $spec = ['object'=>'intent']; } -?> diff --git a/Payload/Invoice.php b/Payload/Invoice.php index ca4326c..e33cdb4 100644 --- a/Payload/Invoice.php +++ b/Payload/Invoice.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Invoice extends ARMObject { - public static $spec = array('object'=>'invoice'); +class Invoice extends ARMObject +{ + public static $spec = ['object'=>'invoice']; } -?> diff --git a/Payload/InvoiceAttachment.php b/Payload/InvoiceAttachment.php new file mode 100644 index 0000000..b678cca --- /dev/null +++ b/Payload/InvoiceAttachment.php @@ -0,0 +1,9 @@ +'invoice_attachment']; +} diff --git a/Payload/InvoiceItem.php b/Payload/InvoiceItem.php index d4f1101..98e99aa 100644 --- a/Payload/InvoiceItem.php +++ b/Payload/InvoiceItem.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class InvoiceItem extends ARMObject { - public static $spec = array('object'=>'invoice_item'); +class InvoiceItem extends ARMObject +{ + public static $spec = ['object'=>'invoice_item']; } -?> diff --git a/Payload/Ledger.php b/Payload/Ledger.php index 5ea2304..c1ec767 100644 --- a/Payload/Ledger.php +++ b/Payload/Ledger.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Ledger extends ARMObject { - public static $spec = array('object'=>'ledger'); +class Ledger extends ARMObject +{ + public static $spec = ['object'=>'ledger']; } -?> diff --git a/Payload/LineItem.php b/Payload/LineItem.php index 1f36c54..00443e4 100644 --- a/Payload/LineItem.php +++ b/Payload/LineItem.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class LineItem extends ARMObject { - public static $spec = array('object'=>'line_item'); +class LineItem extends ARMObject +{ + public static $spec = ['object'=>'line_item']; } -?> diff --git a/Payload/OAuthToken.php b/Payload/OAuthToken.php index 4e5840d..b7c7c64 100644 --- a/Payload/OAuthToken.php +++ b/Payload/OAuthToken.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class OAuthToken extends ARMObject { - public static $spec = array('object'=>'oauth_token', 'endpoint'=>'/oauth/token'); +class OAuthToken extends ARMObject +{ + public static $spec = ['object'=>'oauth_token', 'endpoint'=>'/oauth/token']; } -?> diff --git a/Payload/Org.php b/Payload/Org.php index 6468e9f..079a884 100644 --- a/Payload/Org.php +++ b/Payload/Org.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Org extends ARMObject { - public static $spec = array('object'=>'org', 'endpoint'=>'/accounts/orgs'); +class Org extends ARMObject +{ + public static $spec = ['object'=>'org', 'endpoint'=>'/accounts/orgs']; } -?> diff --git a/Payload/PaymentActivation.php b/Payload/PaymentActivation.php index 9fef072..154ff37 100644 --- a/Payload/PaymentActivation.php +++ b/Payload/PaymentActivation.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class PaymentActivation extends ARMObject { - public static $spec = array('object'=>'payment_activation'); +class PaymentActivation extends ARMObject +{ + public static $spec = ['object'=>'payment_activation']; } -?> diff --git a/Payload/PaymentAllocation.php b/Payload/PaymentAllocation.php index 060bbe9..030dbb3 100644 --- a/Payload/PaymentAllocation.php +++ b/Payload/PaymentAllocation.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class PaymentAllocation extends ARMObject { - public static $spec = array('object'=>'payment_allocation'); +class PaymentAllocation extends ARMObject +{ + public static $spec = ['object'=>'payment_allocation']; } -?> diff --git a/Payload/PaymentLink.php b/Payload/PaymentLink.php index 1cf7a7a..31bfa62 100644 --- a/Payload/PaymentLink.php +++ b/Payload/PaymentLink.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class PaymentLink extends ARMObject { - public static $spec = array('object'=>'payment_link'); +class PaymentLink extends ARMObject +{ + public static $spec = ['object'=>'payment_link']; } -?> diff --git a/Payload/PaymentMethod.php b/Payload/PaymentMethod.php index 8920180..688c870 100644 --- a/Payload/PaymentMethod.php +++ b/Payload/PaymentMethod.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class PaymentMethod extends ARMObject { - public static $spec = array('object'=>'payment_method'); +class PaymentMethod extends ARMObject +{ + public static $spec = ['object'=>'payment_method']; } -?> diff --git a/Payload/ProcessingAccount.php b/Payload/ProcessingAccount.php index 33b5d5b..56a398b 100644 --- a/Payload/ProcessingAccount.php +++ b/Payload/ProcessingAccount.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class ProcessingAccount extends ARMObject { - public static $spec = array('object'=>'processing_account'); +class ProcessingAccount extends ARMObject +{ + public static $spec = ['object'=>'processing_account']; } -?> diff --git a/Payload/ProcessingAgreement.php b/Payload/ProcessingAgreement.php index 555eaae..d2b7ce6 100644 --- a/Payload/ProcessingAgreement.php +++ b/Payload/ProcessingAgreement.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class ProcessingAgreement extends ARMObject { - public static $spec = array('object'=>'processing_agreement'); +class ProcessingAgreement extends ARMObject +{ + public static $spec = ['object'=>'processing_agreement']; } -?> diff --git a/Payload/ProcessingRule.php b/Payload/ProcessingRule.php index 0abce05..b25e1fe 100644 --- a/Payload/ProcessingRule.php +++ b/Payload/ProcessingRule.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class ProcessingRule extends ARMObject { - public static $spec = array('object'=>'processing_rule'); +class ProcessingRule extends ARMObject +{ + public static $spec = ['object'=>'processing_rule']; } -?> diff --git a/Payload/ProcessingSettings.php b/Payload/ProcessingSettings.php index ca515d8..c703cd4 100644 --- a/Payload/ProcessingSettings.php +++ b/Payload/ProcessingSettings.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class ProcessingSettings extends ARMObject { - public static $spec = array('object'=>'processing_settings'); +class ProcessingSettings extends ARMObject +{ + public static $spec = ['object'=>'processing_settings']; } -?> diff --git a/Payload/Profile.php b/Payload/Profile.php index 0a27719..4a1dae1 100644 --- a/Payload/Profile.php +++ b/Payload/Profile.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Profile extends ARMObject { - public static $spec = array('object'=>'profile'); +class Profile extends ARMObject +{ + public static $spec = ['object'=>'profile']; } -?> diff --git a/Payload/Stakeholder.php b/Payload/Stakeholder.php index 219ab64..a903a49 100644 --- a/Payload/Stakeholder.php +++ b/Payload/Stakeholder.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Stakeholder extends ARMObject { - public static $spec = array('object'=>'stakeholder'); +class Stakeholder extends ARMObject +{ + public static $spec = ['object'=>'stakeholder']; } -?> diff --git a/Payload/Transaction.php b/Payload/Transaction.php index a0a2114..60e90fa 100644 --- a/Payload/Transaction.php +++ b/Payload/Transaction.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Transaction extends ARMObject { - public static $spec = array('object'=>'transaction'); +class Transaction extends ARMObject +{ + public static $spec = ['object'=>'transaction']; } -?> diff --git a/Payload/TransactionOperation.php b/Payload/TransactionOperation.php index 22e62e8..ea80333 100644 --- a/Payload/TransactionOperation.php +++ b/Payload/TransactionOperation.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class TransactionOperation extends ARMObject { - public static $spec = array('object'=>'transaction_operation'); +class TransactionOperation extends ARMObject +{ + public static $spec = ['object'=>'transaction_operation']; } -?> diff --git a/Payload/Transfer.php b/Payload/Transfer.php index 879bb64..6020222 100644 --- a/Payload/Transfer.php +++ b/Payload/Transfer.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Transfer extends ARMObject { - public static $spec = array('object'=>'transfer'); +class Transfer extends ARMObject +{ + public static $spec = ['object'=>'transfer']; } -?> diff --git a/Payload/User.php b/Payload/User.php index 2f707b7..2ab50f2 100644 --- a/Payload/User.php +++ b/Payload/User.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class User extends ARMObject { - public static $spec = array('object'=>'user'); +class User extends ARMObject +{ + public static $spec = ['object'=>'user']; } -?> diff --git a/Payload/Utils.php b/Payload/Utils.php index e7d0596..d0dc0ab 100644 --- a/Payload/Utils.php +++ b/Payload/Utils.php @@ -1,49 +1,42 @@ $val ) { + public static function data2object($data) + { + $registry = ARMObject::getRegistry(); + $object = []; + foreach ($data as $key => $val) { $object[$key] = $val; - if ( Utils::is_assoc_array($val) && isset($val['object']) ) { - foreach (Utils::subclasses(ARMObject::class) as $cls) { - if ($cls::$spec['object'] == $val['object']) { - $object[$key] = $cls::new($val); - break; - } + if (Utils::is_assoc_array($val) && isset($val['object'])) { + if (isset($registry[$val['object']])) { + $cls = $registry[$val['object']]; + $object[$key] = $cls::new($val); } - } else if (is_array($val)) { + } elseif (is_array($val)) { $object[$key] = self::data2object($val); } } return $object; } - public static function object2data($object) { - $data = array(); - foreach ( $object as $key => $val ) { + public static function object2data($object) + { + $data = []; + foreach ($object as $key => $val) { $data[$key] = $val; - if ( $val instanceof ARMObject ) { + if ($val instanceof ARMObject) { $data[$key] = $val->data(); - } else if (is_array($val)) { + } elseif (is_array($val)) { $data[$key] = self::object2data($val); } } return $data; } } -?> diff --git a/Payload/Webhook.php b/Payload/Webhook.php index 47aa8b5..a0c62e3 100644 --- a/Payload/Webhook.php +++ b/Payload/Webhook.php @@ -3,7 +3,7 @@ require_once('ARMObject.php'); -class Webhook extends ARMObject { - public static $spec = array('object'=>'webhook'); +class Webhook extends ARMObject +{ + public static $spec = ['object'=>'webhook']; } -?> diff --git a/Payload/WebhookLog.php b/Payload/WebhookLog.php new file mode 100644 index 0000000..985baee --- /dev/null +++ b/Payload/WebhookLog.php @@ -0,0 +1,9 @@ +'webhook_log']; +} diff --git a/Tests/AccountTest.php b/Tests/Int/AccountTest.php similarity index 100% rename from Tests/AccountTest.php rename to Tests/Int/AccountTest.php diff --git a/Tests/BillingTest.php b/Tests/Int/BillingTest.php similarity index 100% rename from Tests/BillingTest.php rename to Tests/Int/BillingTest.php diff --git a/Tests/Fixtures.php b/Tests/Int/Fixtures.php similarity index 100% rename from Tests/Fixtures.php rename to Tests/Int/Fixtures.php diff --git a/Tests/InvoiceTest.php b/Tests/Int/InvoiceTest.php similarity index 100% rename from Tests/InvoiceTest.php rename to Tests/Int/InvoiceTest.php diff --git a/Tests/PaymentLinkTest.php b/Tests/Int/PaymentLinkTest.php similarity index 100% rename from Tests/PaymentLinkTest.php rename to Tests/Int/PaymentLinkTest.php diff --git a/Tests/PaymentTest.php b/Tests/Int/PaymentTest.php similarity index 100% rename from Tests/PaymentTest.php rename to Tests/Int/PaymentTest.php diff --git a/Tests/TransactionTest.php b/Tests/Int/TransactionTest.php similarity index 100% rename from Tests/TransactionTest.php rename to Tests/Int/TransactionTest.php diff --git a/Tests/ARMRequestTest.php b/Tests/Unit/ARMRequestTest.php similarity index 100% rename from Tests/ARMRequestTest.php rename to Tests/Unit/ARMRequestTest.php diff --git a/Tests/Unit/AttrTest.php b/Tests/Unit/AttrTest.php new file mode 100644 index 0000000..0f83031 --- /dev/null +++ b/Tests/Unit/AttrTest.php @@ -0,0 +1,183 @@ +assertEquals('status', strval($attr)); + } + + public function test_constructor_with_null_param() + { + $attr = new Attr(); + $this->assertEquals('', strval($attr)); + } + + public function test_nested_key_via_parent() + { + $parent = new Attr('customer'); + $child = new Attr('name', $parent); + $this->assertEquals('customer[name]', strval($child)); + } + + public function test_deeply_nested_key() + { + $root = new Attr('customer'); + $mid = new Attr('address', $root); + $leaf = new Attr('city', $mid); + $this->assertEquals('customer[address][city]', strval($leaf)); + } + + // --- __get magic method --- + + public function test_get_creates_nested_attr() + { + $attr = new Attr('customer'); + $nested = $attr->name; + $this->assertInstanceOf(Attr::class, $nested); + $this->assertEquals('customer[name]', strval($nested)); + } + + public function test_get_chained_multiple_levels() + { + $attr = new Attr('customer'); + $deep = $attr->address->city; + $this->assertEquals('customer[address][city]', strval($deep)); + } + + // --- eq --- + + public function test_eq_returns_filter_array() + { + $attr = new Attr('status'); + $result = $attr->eq('active'); + $this->assertEquals(['status' => 'active'], $result); + } + + public function test_eq_with_nested_key() + { + $attr = new Attr('customer'); + $result = $attr->name->eq('John'); + $this->assertEquals(['customer[name]' => 'John'], $result); + } + + public function test_eq_with_numeric_value() + { + $attr = new Attr('amount'); + $result = $attr->eq(100); + $this->assertEquals(['amount' => 100], $result); + } + + // --- ne --- + + public function test_ne_prepends_exclamation() + { + $attr = new Attr('status'); + $result = $attr->ne('inactive'); + $this->assertEquals(['status' => '!inactive'], $result); + } + + // --- lt --- + + public function test_lt_prepends_less_than() + { + $attr = new Attr('amount'); + $result = $attr->lt(100); + $this->assertEquals(['amount' => '<100'], $result); + } + + // --- le --- + + public function test_le_prepends_less_than_or_equal() + { + $attr = new Attr('amount'); + $result = $attr->le(100); + $this->assertEquals(['amount' => '<=100'], $result); + } + + // --- gt --- + + public function test_gt_prepends_greater_than() + { + $attr = new Attr('amount'); + $result = $attr->gt(50); + $this->assertEquals(['amount' => '>50'], $result); + } + + // --- ge --- + + public function test_ge_prepends_greater_than_or_equal() + { + $attr = new Attr('amount'); + $result = $attr->ge(50); + $this->assertEquals(['amount' => '>=50'], $result); + } + + // --- contains --- + + public function test_contains_wraps_with_wildcards() + { + $attr = new Attr('name'); + $result = $attr->contains('john'); + $this->assertEquals(['name' => '?*john*'], $result); + } + + // --- in --- + + public function test_in_joins_values_with_pipe() + { + $attr = new Attr('status'); + $result = $attr->in('active', 'pending'); + $this->assertEquals(['status' => 'active|pending'], $result); + } + + public function test_in_single_value() + { + $attr = new Attr('status'); + $result = $attr->in('active'); + $this->assertEquals(['status' => 'active'], $result); + } + + public function test_in_multiple_values() + { + $attr = new Attr('status'); + $result = $attr->in('active', 'pending', 'closed'); + $this->assertEquals(['status' => 'active|pending|closed'], $result); + } + + // --- Comparison methods with nested keys --- + + public function test_ne_with_nested_key() + { + $attr = new Attr('customer'); + $result = $attr->status->ne('inactive'); + $this->assertEquals(['customer[status]' => '!inactive'], $result); + } + + public function test_gt_with_nested_key() + { + $attr = new Attr('order'); + $result = $attr->total->gt(200); + $this->assertEquals(['order[total]' => '>200'], $result); + } + + public function test_contains_with_nested_key() + { + $attr = new Attr('customer'); + $result = $attr->email->contains('example'); + $this->assertEquals(['customer[email]' => '?*example*'], $result); + } + + public function test_in_with_nested_key() + { + $attr = new Attr('customer'); + $result = $attr->type->in('business', 'individual'); + $this->assertEquals(['customer[type]' => 'business|individual'], $result); + } +} diff --git a/Tests/Unit/QueryParamsTest.php b/Tests/Unit/QueryParamsTest.php new file mode 100644 index 0000000..df77fbc --- /dev/null +++ b/Tests/Unit/QueryParamsTest.php @@ -0,0 +1,247 @@ +original_api_version = Payload\API::$api_version ?? null; + $this->original_api_key = Payload\API::$api_key ?? null; + $this->original_api_url = Payload\API::$api_url ?? null; + + Payload\API::$api_key = 'test_key'; + Payload\API::$api_url = 'https://api.test.com'; + Payload\API::$api_version = null; + + $this->curl_requests = []; + $this->mockCurlFunctions(); + } + + protected function tearDown(): void + { + Payload\API::$api_version = $this->original_api_version; + Payload\API::$api_key = $this->original_api_key; + Payload\API::$api_url = $this->original_api_url; + } + + protected function mockCurlFunctions() + { + $test = $this; + + $curl_init = $this->getFunctionMock('Payload', 'curl_init'); + $curl_init->expects($this->any())->willReturnCallback(function ($url) use ($test) { + $handle = 'mock_handle_' . count($test->curl_requests); + $test->curl_requests[$handle] = [ + 'url' => $url, + 'options' => [], + ]; + return $handle; + }); + + $curl_setopt = $this->getFunctionMock('Payload', 'curl_setopt'); + $curl_setopt->expects($this->any())->willReturnCallback(function ($handle, $option, $value) use ($test) { + if (!isset($test->curl_requests[$handle])) { + $test->curl_requests[$handle] = ['options' => []]; + } + $test->curl_requests[$handle]['options'][$option] = $value; + return true; + }); + + $curl_exec = $this->getFunctionMock('Payload', 'curl_exec'); + $curl_exec->expects($this->any())->willReturn(json_encode([ + 'object' => 'list', + 'values' => [ + ['object' => 'customer', 'id' => 'cust_1', 'email' => 'a@example.com', 'name' => 'A'], + ['object' => 'customer', 'id' => 'cust_2', 'email' => 'b@example.com', 'name' => 'B'], + ], + ])); + + $curl_getinfo = $this->getFunctionMock('Payload', 'curl_getinfo'); + $curl_getinfo->expects($this->any())->willReturnCallback(function ($handle, $option = null) { + if ($option === CURLINFO_HTTP_CODE) { + return 200; + } + return null; + }); + + $curl_close = $this->getFunctionMock('Payload', 'curl_close'); + $curl_close->expects($this->any())->willReturn(true); + } + + protected function getRequestUrl() + { + $this->assertNotEmpty($this->curl_requests); + $handle = array_key_first($this->curl_requests); + return $this->curl_requests[$handle]['url']; + } + + protected function getQueryParams() + { + $url = $this->getRequestUrl(); + $query = parse_url($url, PHP_URL_QUERY); + if ($query === null) { + return []; + } + parse_str($query, $params); + return $params; + } + + // --- order_by tests --- + + public function test_order_by_on_arm_request() + { + $req = new Payload\ARMRequest(Payload\Customer::class); + $req->order_by('created_at')->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('order_by', $params); + $this->assertContains('created_at', (array)$params['order_by']); + } + + public function test_order_by_on_arm_object() + { + Payload\Customer::order_by('created_at')->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('order_by', $params); + $this->assertContains('created_at', (array)$params['order_by']); + } + + public function test_order_by_multiple_fields() + { + Payload\Customer::order_by('created_at', 'name')->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('order_by', $params); + $order_by = (array)$params['order_by']; + $this->assertContains('created_at', $order_by); + $this->assertContains('name', $order_by); + } + + // --- limit tests --- + + public function test_limit_on_arm_request() + { + $req = new Payload\ARMRequest(Payload\Customer::class); + $req->limit(10)->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('limit', $params); + $this->assertEquals(10, $params['limit']); + } + + public function test_limit_on_arm_object() + { + Payload\Customer::limit(5)->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('limit', $params); + $this->assertEquals(5, $params['limit']); + } + + // --- offset tests --- + + public function test_offset_on_arm_request() + { + $req = new Payload\ARMRequest(Payload\Customer::class); + $req->offset(20)->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('offset', $params); + $this->assertEquals(20, $params['offset']); + } + + public function test_offset_on_arm_object() + { + Payload\Customer::offset(15)->all(); + + $params = $this->getQueryParams(); + $this->assertArrayHasKey('offset', $params); + $this->assertEquals(15, $params['offset']); + } + + // --- chaining tests --- + + public function test_limit_and_offset_chained() + { + Payload\Customer::limit(10)->offset(20)->all(); + + $params = $this->getQueryParams(); + $this->assertEquals(10, $params['limit']); + $this->assertEquals(20, $params['offset']); + } + + public function test_order_by_with_limit_and_offset() + { + Payload\Customer::order_by('created_at')->limit(5)->offset(10)->all(); + + $params = $this->getQueryParams(); + $this->assertContains('created_at', (array)$params['order_by']); + $this->assertEquals(5, $params['limit']); + $this->assertEquals(10, $params['offset']); + } + + public function test_filter_by_with_order_by_limit_offset() + { + Payload\Customer::filter_by( + ['status' => 'active'] + )->order_by('created_at')->limit(25)->offset(50)->all(); + + $params = $this->getQueryParams(); + $this->assertEquals('active', $params['status']); + $this->assertContains('created_at', (array)$params['order_by']); + $this->assertEquals(25, $params['limit']); + $this->assertEquals(50, $params['offset']); + } + + // --- default_params tests --- + + public function test_default_params_applied() + { + $original = Payload\Customer::getDefaultParams(); + Payload\Customer::setDefaultParams(['limit' => 100]); + $this->assertEquals([], Payload\Account::getDefaultParams()); + + Payload\Customer::filter_by(['status' => 'active'])->all(); + + $params = $this->getQueryParams(); + $this->assertEquals(100, $params['limit']); + + Payload\Customer::setDefaultParams($original); + } + + public function test_default_params_do_not_override_explicit() + { + $original = Payload\Customer::getDefaultParams(); + Payload\Customer::setDefaultParams(['limit' => 100]); + + Payload\Customer::limit(5)->all(); + + $params = $this->getQueryParams(); + $this->assertEquals(5, $params['limit']); + + Payload\Customer::setDefaultParams($original); + } + + // --- no params when not set --- + + public function test_no_query_params_when_none_set() + { + Payload\Customer::filter_by([])->all(); + + $url = $this->getRequestUrl(); + $this->assertStringNotContainsString('order_by', $url); + $this->assertStringNotContainsString('limit', $url); + $this->assertStringNotContainsString('offset', $url); + } +} diff --git a/composer.json b/composer.json index d8b0a73..7ac8ed7 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "minimum-stability": "stable", "prefer-stable": true, - "version": "0.4.0", + "version": "1.0.0", "autoload": { "psr-4": { "Payload\\": "Payload/" diff --git a/import.php b/import.php index c5483cc..2adb56a 100644 --- a/import.php +++ b/import.php @@ -13,7 +13,9 @@ require(dirname(__FILE__) . '/Payload/BillingSchedule.php'); require(dirname(__FILE__) . '/Payload/BillingCharge.php'); require(dirname(__FILE__) . '/Payload/LineItem.php'); +require(dirname(__FILE__) . '/Payload/Ledger.php'); require(dirname(__FILE__) . '/Payload/Webhook.php'); +require(dirname(__FILE__) . '/Payload/WebhookLog.php'); require(dirname(__FILE__) . '/Payload/PaymentActivation.php'); require(dirname(__FILE__) . '/Payload/AccessToken.php'); require(dirname(__FILE__) . '/Payload/ClientToken.php'); @@ -22,6 +24,7 @@ require(dirname(__FILE__) . '/Payload/BillingItem.php'); require(dirname(__FILE__) . '/Payload/Intent.php'); require(dirname(__FILE__) . '/Payload/InvoiceItem.php'); +require(dirname(__FILE__) . '/Payload/InvoiceAttachment.php'); require(dirname(__FILE__) . '/Payload/PaymentAllocation.php'); require(dirname(__FILE__) . '/Payload/Entity.php'); require(dirname(__FILE__) . '/Payload/Stakeholder.php');