Entries tagged php

Quick and dirty redirect script for the fitting shipping provider

Posted on 2. März 2017 Comments

Another way to use the PHP library shipping-service-provider-check apart from fixing the shipping providers in the ERP Plentymarkets is a simple quick and dirty PHP script that redirects to the tracking page of a fitting shipping provider. You can find the whole sourcecode on GitHub.

Installation

If you don’t use some sort of MVC this is a quick way to do it. If you do, I suspect you’ll know how to alter this example to fit your system. Feel free to write me an e-mail or comment if you need help

  1. Log onto your server and create a folder
  2. Copy or git pull the files, including .htaccess
  3. Get Composer
  4. composer require repat/shipping-service-provider-check

Explanation of the code

The $trackingId variable is received via HTTP GET. This means the URL would be http://url.tld/trackingscriptfolder/script.php?tracking_id=123456 (or without the script.php in case of the .htaccess), where 123456 is the TrackingID. The rest is more or less just for debugging. The scripts returns a HTTP Code 422 and ends if there is no input given.

$trackingId = $_GET["tracking_id"];
if (empty($trackingId)) {
  $unprocessableEntity = 422;
  http_response_code($unprocessableEntity);
  echo "wrong input";
  return;
}

Next the URLs will be defined. The keys have to match the shipping providers so make sure the shipping providers are added at the library as well. The TrackingID will be added at the end of the URL, so make sure they have the right format.

$shippingProviderURLs = [
  "dhl" => 'https://nolp.dhl.de/nextt-online-public/set_identcodes.do?idc=',
...
];

Then follows the documented way of checking for the providers. It could technically be that at the same time a TrackingID is valid for more than one provider. To keep things simple, we’ll just use the first one (array_search instead of array_keys). In case there is none found, array_search returns false and the script continues. In case there is one found, the header() function is called to redirect the user to the correct provider page.

$checkedProvider = array_search(true, $result);
$urlOfCheckedProvider = $shippingProviderURLs[$checkedProvider];

if ($checkedProvider !== false) {
  header('location: ' . $urlOfCheckedProvider . $trackingId);
}

Now it’s up to you what you will do with the ones you can’t redirect. I decided to write a little skeleton.html file and include that with a simple message that the user could try the shipping providers where a check is not possible (no API, no website scraping, no regex). They are listed earlier:

$shippingProviderURLsNoCheck = [
  "DPD" => 'https://tracking.dpd.de/parcelstatus?query=',
...

Flattr this!

Fix shipping providers for FBA orders in Plentymarkets

Posted on 27. Februar 2017 Comments

The popular ERP Plentymarkes let’s you send your orders via the Fulfillment by Amazon program to your customers. Amazon is just used as a logistics partner, the order doesn’t have to come via Amazon. It could e.g. be a Rakuten or eBay order. The problem that Plentymarkets didn’t address at all and is discussed in the private forums, is that Amazon chooses whatever shipping company they seem fit.

Every order has a standard shipping provider and with it comes a URL for tracking the package. Most of the vendors would implement an event procedure („Ereignisaktion“) that sends an email to the customer containing the order and the TrackingID as soon as it’s shipped. The TrackingIDs are directly imported from Amazon. Unfortunately Plentymarkets does not change the shipping provider in case it’s not the default one (which is often the case). So the customer gets wrong information.

In one of last last blogposts I introduced a PHP package to check which shipping provider a TrackingID belongs to by either scraping their website or using a regular expression and filtering for common patterns. In this blog post I want to explain how to use it to fix those orders. You will need a webserver with PHP and MySQL and a little knowledge of programming and Linux.

You can find some simplified code examples on GitHub soon.

  1. Take your FBA orders out of the event procedures that sends the email to the customers, filter e.g. via WarehouseID
  2. Instead, use an event procedure to mark those orders with a flag, e.g. a star (ID 2)
  3. Create a DynamicExport of the type OrderComplete. Filter for your Sent Status (usually 7) and WarehouseID of FBA orders. Include these fields:
    1. OrderID
    2. OrderPackageNo
    3. OrderParcelServiceID
    4. OrderParcelServicePresetID
    5. OrderLastUpdateTimestamp
  4. Implement the SOAP Call GetDynamicExport (will be replaced by REST via Plugin mid-2017)
  5. Write a Cronjob that calls the GetDynamicExport script with the FormatID of the DynamicExport from #2
  6. Create a database table called ‚pm_shipping_providers_check‚ with the fields from #2
  7. Write a script that imports the CSV you downloaded with the GetDynamicExport call into your MySQL database
  8. Create another DynamicExport, this time of the type Order. Include these fields:
    1. OrderID (Synchronisation)
    2. ParcelServiceID (Import)
    3. ParcelServicePresetID (Import)
    4. PackageNo (Import)
  9. Create a table with these fields called ‚pm_shipping_providers_correction
  10. Create a script, that uses the shipping-service-provider-check library to check the every PackageNo in ‚pm_shipping_providers_check‘ and – if the provider is different from the default one – writes it into ‚pm_shipping_providers_correction‘
  11. Implement the SOAP Call SetDynamicImport (see #3)
  12. Write a script that exports the table ‚pm_shipping_providers_correction‘ into a (semicolon seperated) CSV.
  13. Create a cronjob that uploads the file regularly
  14. Create an event procedure on the event PackageNo that sends out emails, but only for the previously flagged orders

Flattr this!

Herausfinden zu welchem Versanddienstleister eine Tracking ID gehört

Posted on 29. Juli 2016 Comments

Wir hatten erst letztens das Problem, dass wir automatisiert Tracking IDs zugespielt bekommen, die von mehreren Versanddienstleistern stammen könnten. Dies ist z. B. beim ERP Plentymarkets der Fall, wenn man Pakete per FBA verschickt. Allerdings nur, wenn die Aufträge nicht von Amazon kommen, sondern ein sog. Multichannel Auftrag vorliegt, also Amazon nur als Logistiker benutzt wird. In der API Rückmeldung steht zwar m.E. der Versanddienstleister, allerdings implementiert Plentymarkets kein Mapping.

Um den richtigen Provider zu finden, ohne jedes mal alle Websites abzuklappern habe ich das PHP Paket shipping-service-providers-check geschrieben und über composer verfügbar gemacht. Es macht automatisiert genau das. Falls es nicht geht, weil z.B. die Flash oder JavaScript im Spiel sind oder es gar keine öffentliche Seite gibt (->Amazon Logistics), kann die Nummer auch anhand des Formats geprüft werden, also mit einem regulärem Ausdruck.

Theoretisch ist es möglich, dass die Tracking ID gleichzeitig bei mehreren Providern für unterschiedliche Sendungen gültig ist. Das ist zugegebenermaßen extrem unwahrscheinlich. Dennoch gibt mein Paket ein Array von Paketdienstleistern, jeweils mit einem Boolean (true/false), zurück.

Code Erklärung

Die Abhängigkeiten sind fabpot/goutte (ein Website scraper) und danielstjules/stringy (für Stringvergleiche). Da ich von PHPs use function Gebrauch mache, ist PHP Version 5.6 notwendig.

Die Klasse Check enthält neben dem Konstruktur, der die TrackingID erwartet, 3 weitere Methoden.

  • getProviders() – gibt alle Provider zurück
  • checkAll($extraProviders) – gibt das eben erwähnte Array zurück. Das hier ist die eigentlich wichtige öffentliche Methode
  • check() – private, wird in einer Schleife von checkAll() aufgerufen

In der Datei default_providers.php sind in einem Array alle implementierten Versanddienstleister aufgeführt. Hier können auch weitere hinzugefügt werden, bzw. in diesem Format an checkAll() übergeben werden. Jeder Dienstleister hat 3 Parameter:

  • base_url – URL, die goutte zusammen mit der tracking ID aufrufen wird
  • filter – HTML Tag auf der Seite, nach dem gesucht bzw. das durchsucht werden soll
  • search_string  – nach diesem String wird in dem HTML Tag gesucht

Der eigentlich wichtige Teil ist deswegen die Methode checkOnline()

$crawler = $this->client->request('GET', $parameters["base_url"] . $this->trackingId);

in dieser Zeile ruft goutte die vorher definierte base_url mit der Tracking ID auf. Die URL muss deshalb im Format http://example.com?tracking_id= vorliegen. Aus der Tracking ID 123456 lautet dann der Aufruf http://example.com?tracking_id=123456.

$crawler->filter($parameters["filter"])->each(function ($node) use ($parameters) {
            if (s($node->text())->contains($parameters["search_string"])) {
                return true;
            }
            return false;

In dem HTML Tag aus dem filter Parameter wird jetzt nach dem String search_string mittels stringys contains() gesucht. Sollte das der Fall sein, wird true zurückgegeben. Da die Funktion each ein Array zurückgibt und es möglich ist, dass das relevante HTML Tag mehrmals vorkommt, wird danach geguckt ob true in diesem array überhaupt vorkommt, auch wenn andere Einträge in diesem Array eben false sind.

return in_array(true, ...)

Dies wird in einer Schleife in checkAll() durchgegangen:

        foreach ($shippingProviders as $shippingProvider => $parameters) {
            $response[$shippingProvider] = $this->check($parameters);
        }

Außerdem ist es durch folgende Zeile möglich einen Versanddienstleister hinzuzufügen oder zu ersetzen (beides passiert durch array_merge())

  if (isset($shippingProviders)) {
            $shippingProviders = array_merge($defaultShippingProviders, $shippingProviders);
        } else {
            $shippingProviders = $defaultShippingProviders;
        }

Die Methode zum Überprüfen des regulärem Ausdrucks checkFormat() ist denkbar simpel:

  boolval(preg_match($parameters["regex"], $this->trackingId))

Da preg_match 1 zurückgibt, falls der Regex zutrifft, und 0 falls nicht (false wenn ein Fehler aufgetreten ist), muss um das Ergebnis noch boolval().

Die Anleitung zum Benutzen des Pakets ist auf GitHub bzw. Packagist. Zur Zeit des Verfassens dieses Artikels sind die folgenden Versanddienstleister implementiert:

  • DHL
  • GLS
  • UPS
  • Hermes
  • Amazon Logistics

Fedex, DPD und TNT gestalten sich schwierig, da diese Informationen per JavaScript nachladen und goutte das nicht beherrscht. Ggf. werde ich nochmal ein npm Paket mit zombie.js und/oder phantomjs schreiben.

Flattr this!

MySQL Workbench Error: line contains NULL byte

Posted on 21. April 2016 Comments

When importing CSV (or other) files into the database, scripts (especially PHP or C-related languages), will stop if there is – for whatever reason – a NULL byte in your file because it signals end of file/string, see Null bytes related issues.

So when importing a file like that with MySQL Workbench you will get this error:

line contains NULL byte

You can solve this by using the commandline tool tr (from coreutils):

tr < file-with-nulls -d '\000' > file-without-nulls

To check if there are any null bytes in your file, use the python IDE and type in:

open('filename.ext').read().index('\0')

Thanks to Pointy from Stackoverflow

Flattr this!

Collect currency exchange rates in a MySQL database with PHP and fixer.io API

Posted on 4. August 2015 Comments

If you work in a company that buys and sells goods in many different currencies, it might be a good idea, to use the latest exchange rates. Also, it might be useful, to store old exchange rates to clarify/verify old business decisions. If once a day is enough for you, fixer.io offers a free simple Rest API. A lot of the code at my work is written in PHP but I usually use the request library in JavaScript and Python, so I’m using it in this example too. A common PHP solution would be guzzle. But first, get composer (the PHP counterpart to npm or pip):

$ curl -sS https://getcomposer.org/installer | php

$ php composer.phar require rmccue/requests

The mysql_ commands are deprecated (and removed in PHP 7), use mysqli or PDO. Also you should use some sort of framework for the database access, like medoo or a proper ORM. This is just proof of concept.


$base = 'EUR';
$request = Requests::get('https://api.fixer.io/latest?base=' . $base, array('Accept' => 'application/json'));
if ($request->status_code == 200) {
$response = json_decode($request->body);
$GBP = $response->rates->GBP;
$CAD = $response->rates->CAD;
$USD = $response->rates->USD;
$NOK = $response->rates->NOK;
$CNY = $response->rates->CNY;
$rBase = mysql_real_escape_string($response->base);
$date = mysql_real_escape_string($response->date);
$currencies = mysql_real_escape_string("1.0, $USD, $GBP, $NOK, $CNY, $CAD");
$qry = "INSERT INTO `exchange_rates_fixerio`(date, base, eur, usd, gbp, nok, cny, cad) VALUES ('$date', '$rBase', $currencies);";
$insert = mysql_query($qry, $mysqlConnection) or print mysql_error();
}

I assume the database connection is defined earlier, there’s lot’s of documentation for that. Because we are from Europe, I chose Euro (EUR) as the base currency. Apart from the get() method, you need nothing else, to send a request. If the request returns an OK(200), the response is read and saved into different variables, e.g. for British Pounds, US Dollar, Canadian Dollar, Chinese Renminbi and Norwegian Krone. Just to make sure we have the right base, it’s also parsed. From there it’s only a simple INSERT INTO (as said before, use a framework for that)

The table could look like this:

CREATE TABLE `echange_rates_fixerio` (
`date` date NOT NULL,
`base` varchar(3) NOT NULL,
`eur` double NOT NULL,
`usd` double NOT NULL,
`gbp` double NOT NULL,
`nok` double NOT NULL,
`cny` double NOT NULL,
`cad` double NOT NULL
)

 

You can also find this code on GitHub.

Flattr this!

upload mit phpsec library und php

Posted on 22. Juli 2015 Comments

In der Doku, die gerade nicht verfügbar ist, da sie auf sourceforge liegt, ist ein Fehler. Der Upload via SFTP mit der phpseclib funktioniert auf 2 verschiedene Weisen folgendermaßen:

$sftp->put($destinationFilename, file_get_contents($sourceFullFilePath));

oder


$sftp->put($destinationFilename, $sourceFullFilePath, NET_SFTP_LOCAL_FILE);

 

Flattr this!

How to change nano syntax highlighting for arbitrary filename extensions

Posted on 17. Dezember 2014 Comments

The text editor nano manages syntax highlighting through .nanorc files. Say, we have files with the ending .phpx, but would like PHP syntax highlighting.

$ sudo nano /usr/share/nano/php.nanorc

Change this line

syntax "php" "\.php[2345s~]?$"

to this regex:

syntax "php" "\.php[x2345s~]?$".

Alternativly, e.g. if you don’t have sudo rights for /usr/share/nano/php.nanorc, you can do the following

$cp /usr/share/nano/php.nanorc ~/.phpx.nanorc

Then enter the following line in ~/.nanorc:

include ~/.phpx.nanorc

Then, change the line as mentioned above and syntax „php“ into syntax „phpx“

Flattr this!

Automize selling at LaRedoute #2: Parse order files

Posted on 16. Dezember 2014 Comments

This blog post is part of the series Automize selling at LaRedoute.

  • Part 1: Get new orders
  • Part 2: Parse order files
  • Part 3: Upload response files
  • Part 4: update quantity and price feed

Update 2016: La Redoute is going to stop using CSV and moves everything to SOAP Webservices. Tutorials will follow


In part 1 the files containing orders are downloaded into a folder called OrdersFromLaRedoute. The next script is going to go through that folder, parse the file and insert it into a table.

These are the values the table must have because we’re just going to insert everything that’s in the CSV files.


$dbValues = ['MarketplaceID', 'OrderID', 'StorefrontOrderID', 'OrderDate', 'BuyerEmailAddress', 'BuyerName', 'BuyerPhoneNumber', 'OrderItemCode', 'ItemStatus', 'SKU', 'Title', 'Quantity', 'ItemPrice', 'ItemTax', 'ShippingCharge', 'ShippingTax','ItemFee', 'Currency', 'ShippingOption', 'PaymentInfo', 'ShippingAddressName', 'ShippingAddressFieldOne', 'ShippingAddressFieldTwo', 'ShippingAddressFieldThree', 'ShippingCity', 'ShippingStateOrRegion', 'ShippingPostalCode', 'ShippingCountryCode', 'ShippingPhoneNumber', 'BillingAddressName', 'BillingAddressFieldOne', 'BillingAddressFieldTwo', 'BillingAddressFieldThree', 'BillingCity', 'BillingStateOrRegion', 'BillingPostalCode', 'BillingCountryCode', 'BillingPhoneNumber'];

 

Therefore I created the table more or less like this:

CREATE TABLE `ORDERS-HISTORY` (
`MarketplaceID` int(11) DEFAULT NULL,
`OrderID` varchar(50) DEFAULT NULL,
`StorefrontOrderID` varchar(50) DEFAULT NULL,
`OrderDate` varchar(50) DEFAULT NULL,
`BuyerEmailAddress` varchar(50) DEFAULT NULL,
`BuyerName` varchar(50) DEFAULT NULL,
`BuyerPhoneNumber` varchar(50) DEFAULT NULL,
`OrderItemCode` varchar(50) NOT NULL DEFAULT '',
`ItemStatus` varchar(10) NOT NULL DEFAULT '',
`SKU` int(11) DEFAULT NULL,
`Title` varchar(50) DEFAULT NULL,
`Quantity` int(11) DEFAULT NULL,
`ItemPrice` decimal(8,2) DEFAULT NULL,
`ItemTax` decimal(8,2) DEFAULT NULL,
`ShippingCharge` decimal(8,2) DEFAULT NULL,
`ShippingTax` decimal(8,2) DEFAULT NULL,
`ItemFee` decimal(8,2) DEFAULT NULL,
`Currency` varchar(3) DEFAULT NULL,
`ShippingOption` varchar(10) DEFAULT NULL,
`PaymentInfo` varchar(50) DEFAULT NULL,
`ShippingAddressName` varchar(70) DEFAULT NULL,
`ShippingAddressFieldOne` varchar(70) DEFAULT NULL,
`ShippingAddressFieldTwo` varchar(70) DEFAULT NULL,
`ShippingAddressFieldThree` varchar(70) DEFAULT NULL,
`ShippingCity` varchar(70) DEFAULT NULL,
`ShippingStateOrRegion` varchar(70) DEFAULT NULL,
`ShippingPostalCode` int(11) DEFAULT NULL,
`ShippingCountryCode` varchar(3) DEFAULT NULL,
`ShippingPhoneNumber` varchar(50) DEFAULT NULL,
`BillingAddressName` varchar(70) DEFAULT NULL,
`BillingAddressFieldOne` varchar(70) DEFAULT NULL,
`BillingAddressFieldTwo` varchar(70) DEFAULT NULL,
`BillingAddressFieldThree` varchar(70) DEFAULT NULL,
`BillingCity` varchar(70) DEFAULT NULL,
`BillingStateOrRegion` varchar(70) DEFAULT NULL,
`BillingPostalCode` int(11) DEFAULT NULL,
`BillingCountryCode` varchar(3) DEFAULT NULL,
`BillingPhoneNumber` varchar(50) DEFAULT NULL,
PRIMARY KEY (`OrderID`,`OrderItemCode`,`ItemStatus`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

As you can see, the primary key consists of OrderID, OrderItemCode and ItemStatus. An OrderID is the unique identifier of one order, consisting of possibly many but at least one OrderItemCode. An OrderItemCode is representing an SKU + Quantity. For newly created orders, the ItemStatus will appear as „Created“. Once an item is accepted, LaRedoute will put a file into the ToSupplier folder with exactly the same OrderID, OrderItemCode but the ItemStatus „ToShip“. This will be important in Step 3.

Then, use a CSV library like league/csv and insert it with e.g. medoo.


$dirAsArray = scandir("OrdersFromLaRedoute");

foreach($dirAsArray as $file) {
// parse and INSERT
}

Flattr this!

Automize selling at LaRedoute #1: Get new orders

Posted on 16. Dezember 2014 Comments

This blog post is part of the series Automize selling at LaRedoute.


The french marketplace LaRedoute unfortunately doesn’t have a real API, but they do have ways to automize some processes. A lot of smaller marketplaces have this concept as well. You will get credentials for an SFTP server. On this server you will find the folders ToSupplier and FromSupplier, where the „supplier“ (aka you) can up- and download a range files documented by Merchantry in their blog. The processing of the uploaded files can take up to 6 hours, but is sometimes done in only a couple of minutes, so I’m going to assume the worst case of 6 hours in this post.

While programming a couple of scripts I found the following problems:

  • the server is incredibly slow sometimes (better at nights), so sometimes the connections just time out
  • sometimes the listing for the ToSupplier folder times out because there are too many files (according to support…huh?), so they have to be deleted regularly
  • not only the connection to LaRedoute but also the connection to my local MySQL server times out
  • I have to reserve a purchased item once I accepted it on LaRedoute immediately, because it could be sold elsewhere in the 6 hours LaRedoute might take to give me the shipping address

New orders can be found in the ToSupplier folder in tab seperated CSV files (but .txt ending) with the format OrdersYYYY-MM-DD-hh-mm-ss.txt.

Since PHP is the companies main language I will show a couple of scripts which automize downloading and processing those files. The code is of course simplified for better understanding. We’re using SFTP instead of FTP and I found using the phpseclib to be the most usable library.

I will propose the use of 2 Tables in the MySQL database: TEMP-FILENAMES and FILENAMES-HISTORY. Both have a the unique column filename. FILENAMES-HISTORY will contain the name of every file ever processed by the following script, TEMP-FILENAMES is a helper table that will be truncated after every run.

First, we need to establish a connection


$sftp = new Net_SFTP(SFTP_LAREDOUTE_HOST);
if (!$sftp-&gt;login(SFTP_LAREDOUTE_USER, SFTP_LAREDOUTE_PASS)) {
exit('Login Failed');
}

Then we change directory. This is a command that usually involves listing the directory changed into, but since this is not a graphical client, the real timeout might come on line below. $nlist will just be null if the listing fails, and I will assume it didn’t work if it takes more than 30 seconds.

$sftp->chdir('/ToSupplier');

$beforetime = time();
$nlist = $sftp->nlist();
$aftertime = time();
if(($aftertime-$beforetime) > 30 ) {
exit('Timeout while Listing directory');
}

The next piece of code is only executed if the listing worked. Every filename that includes the word „Order“ is now inserted into the temporary table:

foreach($nlist as $filename) {
if (strpos($filename, 'Order') !== false) {
$qry = "INSERT INTO `TEMP-FILENAMES`(`filename`) VALUES ('". $filename . "')";
$insert = mysql_query($qry,MYSQLCONNECTION) or print mysql_error();
}
}

You can look at the difference between the filenames in your HISTORY table and the possibly new ones in the temporary table.

$tmpCmpFilenames = array();
$qry = "SELECT `filename` FROM `TEMP-FILENAMES` WHERE `filename` NOT IN (SELECT `filename` FROM `FILENAMES-HISTORY`)";
$select = mysql_query($qry, MYSQLCONNECTION) or print mysql_error();
while ($row = mysql_fetch_assoc($select)) {
$tmpCmpFilenames[] = $row['filename-id'];
}

Now we have all the new files in the array $tmpCmpFilenames. The correct way would be make sure the downloaded files are correct with hashes. Instead we decided to misuse the filesize, since it’s a good indicator something didn’t work properly;) The files not downloaded correctly are deleted from the array. They will appear next time the script is run.

foreach($tmpCmpFilenames as $filename) {
$remotefilesize = $sftp->size($filename);
$sftp->get($filename, 'OrdersFromLaRedoute/' . $filename);
$localfilesize = filesize('OrdersFromLaRedoute/' . $filename);
if ($remotefilesize != $localfilesize) {
unset($tmpCmpFilenames[$filename]);
}
}

We can now insert the filenames into the HISTORY table.

foreach($tmpCmpFilenames as $filename) {
$qry = "INSERT INTO `FILENAMES-HISTORY`(`filename`) VALUES ('". $filename . "')";
$insert = mysql_query($qry, MYSQLCONNECTION) or print mysql_error();
}

Last but not least, the temporary table needs to be truncated for the next run.

$truncate=mysql_query("TRUNCATE TABLE `TEMP-FILENAMES`",MYSQLCONNECTION) or print mysql_error();

The next step is described in part 2 of this series.

Flattr this!

Anleitung: Produkte bei eBay über API mit PHP SDK listen – Teil 2: XML Dateien mit PHP erstellen

Posted on 10. Oktober 2014 Comments

Dieser Blog Post ist Teil der Reihe Produkte bei eBay listen.


2.1. XMLWriter

Für das Schreiben von XML in PHP wird hier die XMLWriter Klasse verwendet, sie sollte eigentlich überall vorhanden sein. Als erstes wird ein Objekt erstellt.

$writer = new XMLWriter();

Mit dem folgenden Code kann man zwischen Ausgabe im Browser/auf der Konsole und dem Schreiben in eine .xml-Datei hin- und herschalten.

if ($DEBUG) {
$writer->openURI('php://output');
} else {
$filename = 'AddFixedPriceItem.xml';
touch($filename);
$writer->openURI($filename);
}

Der XMLWriter macht allerdings keine Absätze und so würden die folgende Anweisungen alles in eine Zeile schreiben. Prinzipiell ist das natürlich erstmal nicht unbedingt ein Problem. Allerdings wird eBay die Datei ab einer bestimmten Zeilenlänge nicht mehr akzeptieren, (wahrscheinlich) da die Zeilenanzahl ein Kriterium für die maximale Größe von BulkDataExchangeRequests sind.

$writer->setIndent(true);

Bevor die Elemente geschrieben werden, wird erst einmal das Dokument mit Version und Encoding begonnen.

$writer->startDocument('1.0', 'UTF-8');

Ergebnis:

<?xml version="1.0" encoding="UTF-8"?>

Ab jetzt können beliebig Elemente geschrieben werden. Übergeordnete Elemente können mit den folgenden Befehlen geöffnet und geschlossen werden. Dabei ist beim Schließen der Name egal, es zählt die Reihenfolge. Gerade in Schleifen sollte man hier also genau hinsehen.

$writer->startElement('BulkDataExchangeRequests');
...
$writer->endElement();

Ergebnis:

<BulkDataExchangeRequest>
...
</BulkDataExchangeRequest>

Soll ein Element mit einem Wert geschrieben werden wird der folgende Befehl verwendet

$writer->writeElement('SiteID', '77');

Ergebnis

<SiteID>77</SiteID>

Nun gibt es noch den seltenen Fall, dass in einem Tag noch ein Attribut vorhanden ist. Dies wird folgendermaßen realisiert:

$writer->startElement('ShippingServiceCost');
$writer->writeAttribute('currency', 'EUR');
$writer->text('0.0');
$writer->endElement();

Ergebnis:

<ShippingServiceCost currency="EUR">0.0</ShippingServiceCost>

Zuletzt sollte das Dokument noch geschlossen und der Puffer geschrieben (entweder in die Ausgabe oder in die Datei) werden:

$writer->endDocument();
$writer->flush();

2.2. Dateien zippen

In den Beispielen, die in Teil 3 benutzt werden, wird die .xml-Datei noch komprimiert, bevor sie hochgeladen wird. Dies kann mit dem folgenden Snippet umgesetzt werden.

if (!$DEBUG) {
$gzfile = $filename . ".gz";
$fp = gzopen($gzfile, 'w9');
gzwrite($fp, file_get_contents($filename));
gzclose($fp);
}

Flattr this!