views:

66

answers:

6

When you send a PDT transaction ID back to paypal, you get back a list of transaction data. It has SUCCESS on the first line, and then a list of key=value pairs. One pair per line. For example:

SUCCESS
[email protected]
charset=windows-1252
custom=
first_name=Alice
handling_amount=0.00
invoice=NN0005
item_name=Bear
item_number=BEAR05
last_name=Foobar
mc_currency=SEK
mc_fee=13.00
mc_gross=250.00
[email protected]
payer_id=UC9DXVX7GRSTN
payer_status=unverified
payment_date=09:08:06 Oct 18, 2010 PDT
payment_fee=
payment_gross=
payment_status=Completed
payment_type=instant
protection_eligibility=Ineligible
quantity=1
receipt_id=2479-2605-1192-2880
[email protected]
receiver_id=8Y670ENTB8BY6
residence_country=NO
shipping=0.00
tax=0.00
transaction_subject=Bear
txn_id=1PH815997L239283J
txn_type=web_accept

What is a good, quick and clean way to check if the first line equals SUCCESS and then convert this into an associative array? I am able to do it, and it works, but I'm curious if there are better or cleaner ways of doing it cause what I end up with isn't always that nice. Notice that some of the keys don't have any value as well.

So, what I would like to end up with is essentially:

array(
    'business' => '[email protected]',
    'charset' => 'windows-1252',
    'custom' => NULL,
    'first_name' => Alice,

    // And so on

);

The order doesn't matter.

Update: Thanks for great suggestions! Testing them out now. Splitting the string into separate lines is also part of my problem by the way. Forgot to specify that. See that some has taken that into account and some don't. It can have an impact, since certain methods needs to first split into lines and then the pairs, while others can just eat the whole thing in one piece.

Update: I should also have mentioned that having empty ones end up as NULL would be a bonus, but probably not a requirement. They don't do that in my version either and it doesn't really matter that much.


Benchmark results

Got curious to what I should choose here, so I benchmarked how I should do different parts. Had various ideas myself, got some from here and other places as well. When I had found the fastest I could manage, I created a benchmark and put it to the test against all of the answers so far. For the ones that had skipped the splitting or the checking for success I added an explode and a strpos check accordingly. I also added urldecode to all the solutions as well, except @dynamism who tackled that so nicely. Anyways, here are the results:

Benchmark results

The benchmark was run using the codebench Kohana 3 module. Here is the benchmark code:

<?php defined('SYSPATH') or die('No direct script access.');

/**
 * Test various methods of checking for SUCCESS
 *
 * @package  PayPal
 * @category PDT
 * @author  Torleif Berger
 */
class Bench_ProcessPDT extends Codebench
{
    public $description = 'Various ways of checking that a string starts with SUCCESS';

    public $loops = 100000;


    public function bench_mine($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = urldecode(substr($subject, 7));
            preg_match_all('/^([^=]++)=(.*+)/m', $subject, $result, PREG_PATTERN_ORDER);
            $result = array_combine($result[1], $result[2]);

            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964308#3964308
    public function bench_dynamism_substr($subject)
    {
        if(substr($subject, 0, 7) == 'SUCCESS')
        {
            $subject = substr_replace($subject, '', 0, 7);
            $subject = str_replace(array("\n", "\r", "\r\n"), '&', $subject);
            parse_str($subject, $result);

            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964308#3964308
    public function bench_dynamism_strpos($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = substr_replace($subject, '', 0, 7);
            $subject = str_replace("\r\n", '&', $subject);
            parse_str($subject, $result);

            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964520#3964520
    public function bench_mellowsoon($subject)
    {
        $subject = urldecode($subject);

        $lines = explode("\r\n", $subject);
        $lines = array_map('trim', $lines);
        $status = array_shift($lines);
        if($status == 'SUCCESS')
        {
            $result = array();
            foreach($lines as $line)
            {
                list($key, $value) = explode('=', $line, 2);
                $result[$key] = $value;
            }
            return array(count($result), array_shift($result), array_shift($result));
        }

        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964265#3964265
    public function bench_amber($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = explode("\r\n", urldecode($subject));
            array_shift($subject);  // Remove is empty

            $result = array();
            foreach($subject as $line)
            {
                $bits = explode('=', $line);
                $field_name = array_shift($bits);
                $field_contents = implode('=', $bits);
                $result[$field_name] = $field_contents;
            }
            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964366#3964366
    public function bench_GigaWatt($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = explode("\r\n", urldecode($subject));

            $result = array();
            foreach($subject as $line)
            {
                if (strpos($line, "=") === FALSE)
                    continue;

                list($var, $value) = explode("=", trim($line));
                $result[$var] = empty($value) ? NULL : $value;
            }
            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964366#3964366
    public function bench_GigaWatt2($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = explode("\r\n", urldecode($subject));

            $result = array();
            foreach($subject as $line)
            {
                if (strpos($line, "=") === FALSE)
                    continue;

                list($var, $value) = explode("=", trim($line));
                $result[$var] = $value;
            }
            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }

    // http://stackoverflow.com/questions/3964219/3964323#3964323
    public function bench_dvhh($subject)
    {
        if(strpos($subject, 'SUCCESS') === 0)
        {
            $subject = explode("\r\n", urldecode($subject));

            $result = array();
            foreach($subject as $line)
            {
                $lineData = preg_split("/\s*=\s*/", $line);
                if(count($lineData) == 2)
                {
                    $result[$lineData[0]] = $lineData[1];
                }
            }
            return array(count($result), array_shift($result), array_shift($result));
        }
        return FALSE;
    }


    public $subjects = array
    (
        "SUCCESS\r\[email protected]\r\ncharset=windows-1252\r\ncustom=\r\nfirst_name=Alice\r\nhandling_amount=0.00\r\ninvoice=AF000001\r\nitem_name=Stuffed bear\r\nitem_number=BEAR05\r\nlast_name=Foobar\r\nmc_currency=USD\r\nmc_fee=2.00\r\nmc_gross=20.00\r\[email protected]\r\npayer_id=UC9DXVX7GRSTN\r\npayer_status=unverified\r\npayment_date=09:08:06 Oct 18, 2010 PDT\r\npayment_fee=\r\npayment_gross=\r\npayment_status=Completed\r\npayment_type=instant\r\nprotection_eligibility=Ineligible\r\nquantity=1\r\nreceipt_id=2479-2605-1192-2880\r\[email protected]\r\nreceiver_id=8Y670ENTB8BY6\r\nresidence_country=USD\r\nshipping=0.00\r\ntax=0.00\r\ntransaction_subject=Bear\r\ntxn_id=1PH815997L239283J\r\ntxn_type=web_accept",

        "FAIL\r\nError: 4003",

        "INVALID",
    );
}

If anyone has any further tips for improvement, please let me know :)

+2  A: 

Split off the first line separately, check it, and then use this to grab the rest:

foreach($response_lines as $line) {
    $bits = explode('=', $line);
    $field_name = array_shift($bits);
    $field_contents = implode('=', $bits);
    $fields[$field_name] = $field_contents;
}

After which $fields will be the array you seek. (One note: items without a value in the response will have '' instead of NULL.)

Amber
You could have cut out a couple lines of code by using 2 as the third parameter to explode(), i.e. explode('=', $line, 2); That prevents the string from being split into more than 2 pieces.
mellowsoon
A: 
dynamism
now *that* is interesting... wouldn't have come up with something like that in a million years :p
Svish
oh, this takes care of the urldecoding too. had forgotten about that. nice :)
Svish
`strpos($subject, 'SUCCESS') === 0` is faster than `substr($paypal, 0, 7) == 'SUCCESS'`
Svish
Accepted yours as an answer since it was so clever and also the quickest of the answers here :)
Svish
Glad I could help dude! Nice to know about the strpos. Also, glad to see another Kohana user on here =)
dynamism
Also, yours is really interesting too. Wouldn't have ever thought of that!
dynamism
A: 

use http://www.php.net/manual/en/function.preg-split.php

foreach($lines as $line) {
    $lineData = preg-split("\s*=\s*",$line);
    if(count($lineData)==2) {
         $result[$lineData[0]] = $lineData[1];
    }
}
dvhh
From php.net -> If you don't need the power of regular expressions, you can choose faster (albeit simpler) alternatives like explode() or str_split().
mellowsoon
@mellowsoon: Sometimes regex can be faster, but most likely not in this case, no...
Svish
A: 

The following will work:

foreach($response as $line) {
    if (strpos($line, "=") === FALSE) { continue; }   // Skip the line if there's no assignment action going on.
    list($var, $value) = explode("=", trim($line));   // Get the parts on either side of the "=".
    $result[$var] = (empty($value) ? NULL : $value);  // Assign the value to the result array, making the value NULL if it's empty.
}
GigaWatt
You're the first one to set empty values to NULL, nice :) Lines should not be skipped if they have empty values though. Also, you should add 2 for explode limit.
Svish
A: 

Adding my 2 cents because some of the solutions posted so far are being overly clever, or not 100% correct.

$lines = explode("\n", $paypal_response);
$lines = array_map('trim', $lines);
$status = array_shift($lines);
if ($status != 'SUCCESS') {
    // Transaction was not successful
}
$values = array();
foreach($lines as $line) {
    list($key, $value) = explode('=', $line, 2);
    $values[$key] = $value;
}
mellowsoon
A: 

Here is a more complex solution:

//$result=the result from paypal
parse_str(str_replace(PHP_EOL,'&',$result),$result);
var_dump($result);
Itay Moav