Skip to content

Commit

Permalink
Fix un-pageable array results (no href is present), such as invoice/t…
Browse files Browse the repository at this point in the history
…ransactions

Certain nested array results are not always part of a pageable collection, so when
we parse them into a Recurly_Pager object, we need to pre-load the _count property
so we can iterate properly.

In order to properly implement this, an accurate count of XML node->childNodes was
required, which was being thrown off because we weren't ignoring whitespace nodes.
This change then required a slight rewrite of the parser.

Acked-by: Stephen Celis <stephen@recurly.com>
Signed-off-by: Evan Owen <kainosnoema@gmail.com>
  • Loading branch information
kainosnoema committed Jun 1, 2012
1 parent 5a78aec commit 1bb87de
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 15 deletions.
32 changes: 19 additions & 13 deletions lib/recurly/base.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private function addLink($name, $href, $method){

protected static function __parseXmlToNewObject($xml, $client=null) {
$dom = new DOMDocument();
if (!$dom->loadXML($xml)) return null;
if (!$dom->loadXML($xml, LIBXML_NOBLANKS)) return null;

$rootNode = $dom->documentElement;

Expand All @@ -159,7 +159,7 @@ protected static function __parseXmlToNewObject($xml, $client=null) {
protected function __parseXmlToUpdateObject($xml)
{
$dom = new DOMDocument();
if (!$dom->loadXML($xml)) return null;
if (!$dom->loadXML($xml, LIBXML_NOBLANKS)) return null;

$rootNode = $dom->documentElement;

Expand Down Expand Up @@ -218,28 +218,31 @@ protected static function __parseXmlToObject($node, &$object)
continue;
}

if ($node->childNodes->length > 1) {
$new_obj = Recurly_Resource::__createNodeObject($node);
if (!is_null($new_obj))
$object->$nodeName = Recurly_Resource::__parseXmlToObject($node->firstChild, $new_obj);
} else if ($node->childNodes->length == 0) {
// No children
$numChildren = $node->childNodes->length;
if ($numChildren == 0) {
// No children, we might have a link
$href = $node->getAttribute('href');
if (!empty($href)) {
if ($nodeName == 'a') {
$linkName = $node->getAttribute('name');
$method = $node->getAttribute('method');
$object->addLink($linkName, $href, $method);
}
else {
} else {
if (!is_object($object))
$object->$nodeName = new Recurly_Stub($nodeName, $href);
else
$object->$nodeName = new Recurly_Stub($nodeName, $href, $object->_client);
}
}

} else if ($node->firstChild->nodeType == XML_ELEMENT_NODE) {
// has element children, drop in and continue parsing
$new_obj = Recurly_Resource::__createNodeObject($node);
if (!is_null($new_obj))
$object->$nodeName = Recurly_Resource::__parseXmlToObject($node->firstChild, $new_obj);

} else {
// Single element
// we have a single text child
if ($node->hasAttribute('nil')) {
$object->$nodeName = null;
} else {
Expand Down Expand Up @@ -303,14 +306,17 @@ private static function __createNodeObject($node)
else if ($node_class == 'string')
return $node->firstChild->wholeText;
else {
if ($node_class == 'Recurly_CurrencyList')
if ($node_class == 'Recurly_CurrencyList') {
$new_obj = new $node_class($nodeName);
else
} else
$new_obj = new $node_class();

$href = $node->getAttribute('href');
if (!empty($href))
$new_obj->setHref($href);
else if ($new_obj instanceof Recurly_Pager) {
$new_obj->_count = $node->childNodes->length;
}

return $new_obj;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/recurly/pager.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ abstract class Recurly_Pager extends Recurly_Base implements Iterator
{
private $_etag;
private $_position = 0; // position within the current page
private $_count; // total number of records
protected $_count; // total number of records
protected $_objects; // current page of records

/**
Expand Down
89 changes: 89 additions & 0 deletions test/fixtures/invoices/show-200.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8

<?xml version="1.0" encoding="UTF-8"?>
<invoice href="https://api.recurly.com/v2/invoices/abcdef1234567890">
<account href="https://api.recurly.com/v2/accounts/1"/>
<uuid>012345678901234567890123456789aa</uuid>
<state>collected</state>
<invoice_number type="integer">abcdef1234567890</invoice_number>
<po_number nil="nil"></po_number>
<vat_number nil="nil"></vat_number>
<subtotal_in_cents type="integer">2995</subtotal_in_cents>
<tax_in_cents type="integer">0</tax_in_cents>
<total_in_cents type="integer">2995</total_in_cents>
<currency>USD</currency>
<created_at type="datetime">2012-05-28T17:44:13Z</created_at>
<line_items type="array">
<adjustment href="https://api.recurly.com/v2/adjustments/190357944dad4d461272774dd184a17e" type="charge">
<account href="https://api.recurly.com/v2/accounts/1"/>
<invoice href="https://api.recurly.com/v2/invoices/abcdef1234567890"/>
<uuid>012345678901234567890123456789ab</uuid>
<state>invoiced</state>
<description>Silver Plan</description>
<accounting_code></accounting_code>
<origin>plan</origin>
<unit_amount_in_cents type="integer">2995</unit_amount_in_cents>
<quantity type="integer">1</quantity>
<discount_in_cents type="integer">0</discount_in_cents>
<tax_in_cents type="integer">0</tax_in_cents>
<total_in_cents type="integer">2995</total_in_cents>
<currency>USD</currency>
<taxable type="boolean">false</taxable>
<start_date type="datetime">2012-05-28T17:44:13Z</start_date>
<end_date type="datetime">2013-05-28T17:44:13Z</end_date>
<created_at type="datetime">2012-05-28T17:44:13Z</created_at>
</adjustment>
</line_items>
<transactions type="array">
<transaction href="https://api.recurly.com/v2/transactions/012345678901234567890123456789ab" type="credit_card">
<account href="https://api.recurly.com/v2/accounts/1"/>
<invoice href="https://api.recurly.com/v2/invoices/abcdef1234567890"/>
<subscription href="https://api.recurly.com/v2/subscriptions/012345678901234567890123456789aa"/>
<uuid>012345678901234567890123456789ab</uuid>
<action>purchase</action>
<amount_in_cents type="integer">2995</amount_in_cents>
<tax_in_cents type="integer">0</tax_in_cents>
<currency>USD</currency>
<status>success</status>
<reference>12345</reference>
<source>subscription</source>
<recurring type="boolean">false</recurring>
<test type="boolean">true</test>
<voidable type="boolean">true</voidable>
<refundable type="boolean">true</refundable>
<cvv_result code="M">Match</cvv_result>
<avs_result code="D">Street address and postal code match.</avs_result>
<avs_result_street>Y</avs_result_street>
<avs_result_postal>Y</avs_result_postal>
<created_at type="datetime">2012-05-28T17:44:13Z</created_at>
<details>
<account>
<account_code>1</account_code>
<first_name>John</first_name>
<last_name>Doe</last_name>
<company nil="nil"></company>
<email>john@example.com</email>
<billing_info type="credit_card">
<first_name nil="nil"></first_name>
<last_name nil="nil"></last_name>
<address1 nil="nil"></address1>
<address2 nil="nil"></address2>
<city nil="nil"></city>
<state nil="nil"></state>
<zip nil="nil"></zip>
<country nil="nil"></country>
<phone nil="nil"></phone>
<vat_number nil="nil"></vat_number>
<card_type>Visa</card_type>
<year type="integer">2013</year>
<month type="integer">1</month>
<first_six>411111</first_six>
<last_four>1111</last_four>
</billing_info>
</account>
</details>
<a name="refund" href="https://api.recurly.com/v2/transactions/012345678901234567890123456789ab" method="delete"/>
</transaction>
</transactions>
</invoice>
21 changes: 20 additions & 1 deletion test/recurly/invoice_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,27 @@

class Recurly_InvoiceTest extends UnitTestCase
{

public function testGetInvoice()
{
$responseFixture = loadFixture('./fixtures/invoices/show-200.xml');

$client = new MockRecurly_Client();
$client->returns('request', $responseFixture, array('GET', '/invoices/abcdef1234567890'));

$invoice = Recurly_Invoice::get('abcdef1234567890', $client);

$this->assertIsA($invoice, 'Recurly_Invoice');
$this->assertEqual($invoice->state, 'collected');
$this->assertEqual($invoice->total_in_cents, 2995);
$this->assertEqual($invoice->getHref(),'https://api.recurly.com/v2/invoices/abcdef1234567890');
$this->assertIsA($invoice->transactions, 'Recurly_TransactionList');
$this->assertEqual($invoice->transactions->current()->uuid, '012345678901234567890123456789ab');
$this->assertEqual($invoice->transactions->count(), 1);
}

public function testInvoicePendingCharges()
{
{
$responseFixture = loadFixture('./fixtures/invoices/create-201.xml');

$client = new MockRecurly_Client();
Expand Down

0 comments on commit 1bb87de

Please sign in to comment.