PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for April, 2010

Mit der eBay-API seine Auktionen beobachten

with 3 comments

Nach dem letzten Artikel über die eBay-API und das Erstellen von Auktionen wollen wir nun unsere Auktionen beobachten und Informationen periodisch abfragen. Damit können wir uns selbst beispielsweise eine tägliche Übersicht senden oder die Auktionen auf unserer Webseite darstellen.

Die entsprechende API-Funktion heißt GetSellerList, sie liefert uns Informationen über unsere Auktionen eines bestimmten Zeitraums. Wenn man detaillierte Informationen über bereits verkaufte Artikel haben möchte muss man GetSellerTransactions nutzen. Hier stelle ich erstere vor.

Wie bereits im ersten Artikel stelle ich euch hier ein Basis-Script vor, natürlich sollte man das noch umbauen und erweitern, aber zur Erklärung der Funktionalität reicht es aus. Wir bereiten hier einen XML-Request vor mit den entsprechenden Parametern und erhalten eine XML-Antwort, die wir mittels DomDocument auseinander nehmen und die Informationen extrahieren. Hier also das Script:

<?php
$addItem = new eBayGetSellerList();
$addItem->callEbay();
$addItem->printResult();

class eBayGetSellerList
{
	private $_siteId = 77;	// default: Germany
	private $_environment = 'sandbox';   // toggle between sandbox and production
	private $_eBayApiVersion = 661;
	private $_call = 'GetSellerList';
	private $_keys = array(
		'production' => array(
			'DEVID' 	=> '',
			'AppID' 	=> '',
			'CertID' 	=> '',
			'UserToken'	=> '',
			'ServerUrl' => 'https://api.ebay.com/ws/api.dll'
			),
		'sandbox' => array(
			'DEVID'     => '6daxxxxxxxxxxxxxxxxxxxxxxxxxx1e4622',
			'AppID'     => 'Mixxxxxxxxxxxxxxxxxxxxxxxxxxxxxx930',
			'CertID'    => '68xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00e',
			'UserToken' => 'AgAxxxxxxxxlaaaangxxxxxxxxxxIrGgYZ',
			'ServerUrl' => 'https://api.sandbox.ebay.com/ws/api.dll'
		)
	);

	private function _getRequestBody()
	{
		$apiValues = $this->_keys[$this->_environment];

		$dateNow = time();
		$date4weeksAgo = $dateNow-60*60*24*28;

		$search = array(
			'%%USER_TOKEN%%', '%%EBAY_API_VERSION%%', '%%STARTTIMEFROM%%', '%%STARTTIMETO%%'
		);
		$replace = array(
			$apiValues['UserToken'], $this->_eBayApiVersion, date('Y-m-d\TH:i:s.000\Z', $date4weeksAgo), date('Y-m-d\TH:i:s.000\Z', $dateNow)
		);

		$requestXmlBody = file_get_contents('GetInfo.xml');
		$requestXmlBody = str_replace($search,$replace, $requestXmlBody);

		return $requestXmlBody;
	}

	public function callEbay()
	{
		$apiValues = $this->_keys[$this->_environment];

		$connection = curl_init();
		curl_setopt($connection, CURLOPT_URL, $apiValues['ServerUrl']);
		curl_setopt($connection, CURLOPT_SSL_VERIFYPEER, 0);
		curl_setopt($connection, CURLOPT_SSL_VERIFYHOST, 0);

		$headers = array (
			'X-EBAY-API-COMPATIBILITY-LEVEL: ' . $this->_eBayApiVersion,
			'X-EBAY-API-DEV-NAME: ' . $apiValues['DEVID'],
			'X-EBAY-API-APP-NAME: ' . $apiValues['AppID'],
			'X-EBAY-API-CERT-NAME: ' . $apiValues['CertID'],
			'X-EBAY-API-CALL-NAME: ' . $this->_call,
			'X-EBAY-API-SITEID: ' . $this->_siteId,
		);

		curl_setopt($connection, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($connection, CURLOPT_POST, 1);

		$requestBody = $this->_getRequestBody();

		curl_setopt($connection, CURLOPT_POSTFIELDS, $requestBody);
		curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1);
		$responseXml = curl_exec($connection);
		curl_close($connection);
		$this->_responseXml = $responseXml;
		var_dump($responseXml);
	}

	public function printResult()
	{
		//Xml string is parsed and creates a DOM Document object
		$responseDoc = new DomDocument();
		$responseDoc->loadXML($this->_responseXml);

		//get any error nodes
		$errors = $responseDoc->getElementsByTagName('Errors');

		//if there are error nodes
		if($errors->length > 0)
		{
			echo '<P><B>eBay returned the following error(s):</B>';
			//display each error
			//Get error code, ShortMesaage and LongMessage
			$code     = $errors->item(0)->getElementsByTagName('ErrorCode');
			$shortMsg = $errors->item(0)->getElementsByTagName('ShortMessage');
			$longMsg  = $errors->item(0)->getElementsByTagName('LongMessage');
			//Display code and shortmessage
			echo '<P>', $code->item(0)->nodeValue, ' : ', str_replace(">", "&gt;", str_replace("<", "&lt;", $shortMsg->item(0)->nodeValue));
			//if there is a long message (ie ErrorLevel=1), display it
			if(count($longMsg) > 0) {
				echo '<BR>', str_replace(">", "&gt;", str_replace("<", "&lt;", $longMsg->item(0)->nodeValue));
			}

		} else { //no errors
			//get results nodes
			$responses = $responseDoc->getElementsByTagName("GetSellerListResponse");
			foreach ($responses as $response) {
				$acks = $response->getElementsByTagName("Ack");
				$ack   = $acks->item(0)->nodeValue;
				echo "Ack = $ack <BR />\n";   // Success if successful

				$totalNumberOfEntries  = $response->getElementsByTagName("TotalNumberOfEntries");
				$totalNumberOfEntries  = $totalNumberOfEntries->item(0)->nodeValue;
				echo "totalNumberOfEntries = $totalNumberOfEntries <BR />\n";

				$items  = $response->getElementsByTagName("Item");

				for($i=0; $i<$totalNumberOfEntries; $i++) {
					$itemId = $items->item($i)->getElementsByTagName('ItemID')->item(0)->nodeValue;
					$itemUrl = $items->item($i)->getElementsByTagName('ViewItemURL')->item(0)->nodeValue;
					$startTime = $items->item($i)->getElementsByTagName('StartTime')->item(0)->nodeValue;
					$endTime = $items->item($i)->getElementsByTagName('EndTime')->item(0)->nodeValue;
					$bidCount = $items->item($i)->getElementsByTagName('BidCount')->item(0)->nodeValue;
					$priceInEUR = $items->item($i)->getElementsByTagName('ConvertedCurrentPrice')->item(0)->nodeValue;
					$status = $items->item($i)->getElementsByTagName('ListingStatus')->item(0)->nodeValue;
					$title = $items->item($i)->getElementsByTagName('Title')->item(0)->nodeValue;
					$watchCount = $items->item($i)->getElementsByTagName('WatchCount')->item(0)->nodeValue;

					echo "itemID = $itemId <BR />\n";
					echo "itemURL = $itemUrl <BR />\n";
					echo "startTime = $startTime <BR />\n";
					echo "endTime = $endTime <BR />\n";
					echo "bidCount = $bidCount <BR />\n";
					echo "priceInEUR = $priceInEUR <BR />\n";
					echo "status = $status <BR />\n";
					echo "title = $title <BR />\n";
					echo "watchCount = $watchCount <BR />\n";
				}
			}
		}
	}
}

Die GetInfo.xml sieht folgendermaßen aus:

<?xml version="1.0" encoding="UTF-8"?>
<GetSellerListRequest xmlns="urn:ebay:apis:eBLBaseComponents">
	<RequesterCredentials>
		<eBayAuthToken>%%USER_TOKEN%%</eBayAuthToken>
	</RequesterCredentials>
	<ErrorLanguage>en_US</ErrorLanguage>
	<Version>%%EBAY_API_VERSION%%</Version>
	<GranularityLevel>Coarse</GranularityLevel>
	<IncludeWatchCount>true</IncludeWatchCount>
	<StartTimeFrom>%%STARTTIMEFROM%%</StartTimeFrom>
	<StartTimeTo>%%STARTTIMETO%%</StartTimeTo>
	<Pagination>
		<EntriesPerPage>200</EntriesPerPage>
		<PageNumber>1</PageNumber>
	</Pagination>
	<WarningLevel>High</WarningLevel>
</GetSellerListRequest>

Bevor es funktioniert muß man natürlich noch seine API-Keys einfügen, siehe erster Artikel. Um die printResult() Funktion besser zu verstehen hier ein Bespiel des $responseXml:

<?xml version="1.0" encoding="UTF-8"?>
<GetSellerListResponse xmlns="urn:ebay:apis:eBLBaseComponents">
<Timestamp>2010-04-3T11:25:37.003Z</Timestamp>
<Ack>Success</Ack>
<Version>661</Version>
<Build>E661_INTL_BUNDLED_10949245_R1</Build>
<PaginationResult>
	<TotalNumberOfPages>1</TotalNumberOfPages>
	<TotalNumberOfEntries>8</TotalNumberOfEntries>
</PaginationResult>
<HasMoreItems>false</HasMoreItems>
<ItemArray>
	<Item>
		<AutoPay>false</AutoPay>
		<BuyerProtection>ItemIneligible</BuyerProtection>
		<Country>US</Country>
		<Currency>USD</Currency>
		<GiftIcon>0</GiftIcon>
		<HitCounter>NoHitCounter</HitCounter>
		<ItemID>110044434434</ItemID>
		<ListingDetails>
			<StartTime>2010-03-27T12:44:13.000Z</StartTime>
			<EndTime>2010-03-28T12:44:13.000Z</EndTime>
			<ViewItemURL>http://cgi.sandbox.ebay.de/ws/eBayISAPI.dll?ViewItem&amp;item=110044434434&amp;category=14111</ViewItemURL>
			<HasUnansweredQuestions>false</HasUnansweredQuestions>
			<HasPublicMessages>false</HasPublicMessages>
			<BuyItNowAvailable>true</BuyItNowAvailable>
			<ExpressListing>false</ExpressListing>
		</ListingDetails>
		<ListingDuration>Days_1</ListingDuration>
		<Location>San Jose, CA</Location>
		<PrimaryCategory>
			<CategoryID>14111</CategoryID>
			<CategoryName>Everything Else:Test Auctions:General</CategoryName>
		</PrimaryCategory>
		<Quantity>1</Quantity>
		<ReviseStatus>
			<ItemRevised>false</ItemRevised>
		</ReviseStatus>
		<SellingStatus>
			<BidCount>0</BidCount>
			<BidIncrement currencyID="USD">0.25</BidIncrement>
			<ConvertedCurrentPrice currencyID="EUR">1.26</ConvertedCurrentPrice>
			<CurrentPrice currencyID="USD">1.71</CurrentPrice>
			<MinimumToBid currencyID="USD">1.71</MinimumToBid>
			<QuantitySold>0</QuantitySold>
			<SecondChanceEligible>false</SecondChanceEligible>
			<ListingStatus>Completed</ListingStatus>
		</SellingStatus>
		<ShippingDetails>
			<TaxTable/>
		</ShippingDetails>
		<ShipToLocations>US</ShipToLocations>
		<Site>US</Site>
		<TimeLeft>PT0S</TimeLeft>
		<Title>TTTEST IN SANDBOX BEFORE PROD - DO NOT BID</Title>
		<WatchCount>0</WatchCount>
		<PostalCode></PostalCode>
		<PictureDetails>
			<PhotoDisplay>None</PhotoDisplay>
		</PictureDetails>
		<ProxyItem>false</ProxyItem>
		<BuyerGuaranteePrice currencyID="EUR">20000.0</BuyerGuaranteePrice>
		<ReturnPolicy>
			<ReturnsAcceptedOption>ReturnsNotAccepted</ReturnsAcceptedOption>
			<ReturnsAccepted>Returns Not Accepted</ReturnsAccepted>
		</ReturnPolicy>
		<PaymentAllowedSite>US</PaymentAllowedSite>
	</Item>
	.....
	.....
</ItemArray>
<ItemsPerPage>200</ItemsPerPage>
<PageNumber>1</PageNumber>
<ReturnedItemCountActual>8</ReturnedItemCountActual>
</GetSellerListResponse>

Man erkennt natürlich einige Parallelen zur Auktionserstellung und kann das schön in eine Elternklasse auslagern.

Nachdem ich diese Funktion umgesetzt hatte, habe ich noch GetMyeBaySelling gefunden, mit der man die einzelnen Listen (ActiveList, BidList, DeletedFromSoldList, DeletedFromUnsoldList, ScheduledList, SoldList und UnsoldList) erhält, was natürlich sehr viel nützlicher ist.

Written by Michael Kliewe

April 12th, 2010 at 10:29 am

Posted in PHP

Tagged with , , , ,

International PHP Conference in Berlin und PHP Unconference in Hamburg

with 4 comments

Letztes Wochenende wurde der Ticketverkauf der PHP Unconference 2010 in Hamburg gestartet, und nur 6 Tage später waren die 200 Tickets ausverkauft, Wahnsinn! Auch dieses Jahr werde ich wieder mit von der Partie sein, letztes Jahr war spitze und ich bin mir sicher dass ich auch dieses Jahr wieder viel Neues mit nach Hause nehmen werde.

Gestern habe ich noch eine gute Nachricht bekommen: Ich werde dieses Jahr auch zur International PHP Conference Spring Edition 2010 fahren, mein Arbeitgeber mail.de hat mir das OK gegeben und spendiert mir einen mehrtägigen Besuch in Berlin. Danke Fabian, du bist der Beste!

Jetzt muß ich nur noch für die jeweiligen Termine Hotels finden in Berlin bzw. Hamburg. Falls jemand einen Geheimtipp für günstige Unterkünfte hat, nur her damit, ansonsten werd ich es über die zahlreichen Hotel-Bewertungs-Such-Seiten versuchen.

Wer von euch ist noch auf einer der beiden Konferenzen, dann kann man sich evtl. die Gesichter schonmal bei Xing und Co. angucken. Ich hoffe den ein oder anderen Leser dort zu treffen und vielleicht ein kleines Pläuschchen zu halten.

Written by Michael Kliewe

April 8th, 2010 at 9:50 am

Debug Ausgaben im Code vergessen?

with 10 comments

Wahrscheinlich ist es jedem schon einmal passiert: Irgendwo ist ein var_dump() oder file_put_contents(‚/tmp/blub‘) im Code vergessen worden, natürlich in einem sehr unwahrscheinlichen Programmzweig, und unverhofft kommt eine Email mit einer „komischen Ausgabe“ inklusive internen Informationen von einem Benutzer. Aua!

Doch was kann man dagegen machen? Die beste Lösung ist natürlich: Keine Debug-Ausgaben solcher Art benutzen, sondern nur mittels IDE-Debugging/Breakpoints etc. den Code debuggen. Oder aber man verwendet Zend_Log in Verbindung mit dem Firebug-Writer, dann erhält man auch im Browser Debug-Ausgaben, aber nicht störend mitten im Code, sondern im Firebug/FirePHP Addon des Firefox. Außerdem kann man dieses Plugin dann nur für die Development-Umgebung aktivieren, im Produktivbetrieb sollte der Writer natürlich abgeschaltet werden. Dann ist sichergestellt dass keine internen Informationen nach außen dringen.

$logger = new Zend_Log();

// create the file writer
$formatter = new Zend_Log_Formatter_Simple('%timestamp% %priorityName% (%priority%): %message%' . PHP_EOL);
$fileWriter = new Zend_Log_Writer_Stream($tempDirectory . "/logs/" . date("Y-m-d") . ".log");
$fileWriter->setFormatter($formatter);
$logger->addWriter($fileWriter);

if ($debug) {
	$firebugWriter = new Zend_Log_Writer_Firebug();
	$logger->addWriter($firebugWriter);
}

Damit auch sichergestellt ist dass kein Entwickler doch noch var_dumps nutzt und vergisst, schreiben wir auch gleich noch einen Unit-Test, der den Source-Code nach „var_dump“ und „file_put_contents“ durchsucht und Alarm schlägt falls die beiden Funktionen an „nicht erlaubten Stellen“ genutzt werden. Im unten stehenden Beispiel habe ich auch noch einen Test für „console.log()“ Aufrufe im Javascript, denn die können Probleme machen wenn der Browser diese Funktion nicht unterstützt.

Das Script hier ist abgespeckt und muss natürlich an die Gegebenheiten angepasst werden. Es findet zum Beispiel auch auskommentierte var_dumps, das könnte man noch optimieren.

<?php
/*
 * This tests check in testing and stable if there are
 * console.log()  function calls in javascript
 * var_dump()     function calls in php
 *
 **/
class CheckDebuggingOutputTest extends PHPUnit_Framework_TestCase
{
	public function testJavascriptConsoleLog()
	{
		if (strpos(APPLICATION_PATH, 'testing') !== false || strpos(APPLICATION_PATH, 'stable') !== false) {
			$errorString = '';
			$jsList = file(APPLICATION_PATH . '/configs/js_includes.txt', FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
			foreach ($jsList as $jsFile) {
				$content = file(realpath(APPLICATION_PATH . '/../public/js/'.$jsFile), FILE_IGNORE_NEW_LINES);
				foreach ($content as $lineNumber => $line) {
					if (stripos($line, 'console.log') !== false) {
						$errorString .= 'console.log() call found in file '.$jsFile.' line '.($lineNumber+1)."\n";
					}
				}
			}
			if (!empty($errorString)) {
				$this->fail($errorString);
			}
		} else {
			$this->markTestSkipped('Debugging output should only be checked in testing and stable branch');
		}
	}

	public function testPhpVarDump()
	{
		if (strpos(APPLICATION_PATH, 'testing') !== false || strpos(APPLICATION_PATH, 'stable') !== false) {
			$errorString = '';
			// get all files with .php or .phtml extension, except sandbox/ folder
			$iterator = new RecursiveDirectoryIterator(realpath(APPLICATION_PATH.'/../'));
			foreach (new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::CHILD_FIRST) as $file) {
				if ($file->isFile() &&
					strpos($file->getPathname(), '/sandbox')===false &&
					$file->getFilename() != 'CheckDebuggingOutputTest.php' &&
					(substr($file->getFilename(), -3) == 'php' || substr($file->getFilename(), -4) == 'phtml')
				) {
					$content = file($file->getPathname(), FILE_IGNORE_NEW_LINES);
					foreach ($content as $lineNumber => $line) {
						if (stripos($line, 'var_dump') !== false) {
							$errorString .= 'var_dump() call found in file '.$file->getPathname().' line '.($lineNumber+1)."\n";
						}
					}
				}
			}
			if (!empty($errorString)) {
				$this->fail($errorString);
			}
		} else {
			$this->markTestSkipped('Debugging output should only be checked in testing and stable branch');
		}
	}
}

Findet ihr, dass ein solcher Test in die Testsuite reingehört, und der Build fehlschlagen darf wegen eines solchen „Fehlers“? Wie prüft ihr, ob ihr Debug-Ausgaben im Code vergessen habt? Oder nutzt ihr vor jedem Commit/Release die „projektweite Suche“ der IDE, um nach soetwas zu suchen?

Written by Michael Kliewe

April 6th, 2010 at 11:04 am

Linkpool Nummer 4

with 2 comments

Heute nur 4 kurze Links, die es aber in sich haben:

PHP6 ist released!

Unsere geliebte XDebug-Extension wird bald kostenpflichtig

Alle IPv4 Adressen sind vergeben, das Internet muß für 24 Stunden abgeschaltet werden zum Umrüsten:

Weitere wichtige Nachrichten des Tages (unbedingt Lesen, einige wichtige Änderungen wird es geben und tolle neue Produkte!):

Written by Michael Kliewe

April 1st, 2010 at 9:48 pm

Posted in Allgemein

Tagged with ,