Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><strong>Lesson 1</strong>: Don't use PHP's superglobals</p> <ul> <li><code>$_POST</code> = <code>$this-&gt;params['form'];</code></li> <li><code>$_GET</code> = <code>$this-&gt;params['url'];</code></li> <li><code>$_GLOBALS</code> = <code>Configure::write('App.category.variable', 'value');</code></li> <li><code>$_SESSION</code> (view) = <code>$session-&gt;read();</code> (helper)</li> <li><code>$_SESSION</code> (controller) = <code>$this-&gt;Session-&gt;read();</code> (component)</li> <li><code>$_SESSION['Auth']['User']</code> = <code>$this-&gt;Auth-&gt;user();</code></li> </ul> <p>Replacements for <code>$_POST</code>: </p> <pre><code>&lt;?php ... //foreach ($_POST as $key =&gt; $value) { foreach ($this-&gt;params['form'] as $key =&gt; $value) { ... //if (isset($_POST['test_ipn'])) { if (isset($this-&gt;params['form']['test_ipn'])) { ... ?&gt; </code></pre> <p><strong>Lesson 2</strong>: Views are for sharing (with the user)</p> <p>Code documented "Compiles premium info and send the user to Paypal" doesn't send the user to PayPal. Are you redirecting in the view?</p> <pre><code>&lt;?php function redirect($premiumId) { ... $this-&gt;redirect($url . '?' . http_build_query($paypalData), 303); } </code></pre> <p>Redirect at the end of your controller and delete the view. :)</p> <p><strong>Lesson 3</strong>: Data manipulation belongs in model layer</p> <pre><code>&lt;?php class PremiumSite extends AppModel { ... function beforeSave() { if ($this-&gt;data['PremiumSite']['type'] == "1") { $cost = Configure::read('App.costs.premium'); $numberOfWeeks = ((int) $this-&gt;data['PremiumSite']['length']) + 1; $timestring = String::insert('+:number weeks', array( 'number' =&gt; $numberOfWeeks, )); $expiration = date('Y-m-d H:i:s', strtotime($timestring)); $this-&gt;data['PremiumSite']['upfront_weeks'] = $weeks; $this-&gt;data['PremiumSite']['upfront_expiration'] = $expiration; $this-&gt;data['PremiumSite']['cost'] = $cost * $numberOfWeeks; } else { $this-&gt;data['PremiumSite']['cost'] = $cost; } return true; } ... } ?&gt; </code></pre> <p><strong>Lesson 4</strong>: Models aren't just for database access</p> <p>Move code documented "Enables premium site after payment" to PremiumSite model, and call it after payment:</p> <pre><code>&lt;?php class PremiumSite extends AppModel { ... function enable($id) { $transaction = $this-&gt;find('first', array( 'conditions' =&gt; array('PaypalNotification.id' =&gt; $id), 'recursive' =&gt; 0, )); $transactionType = $transaction['PaypalNotification']['txn_type']; if ($transactionType == 'subscr_signup' || $transaction['PaypalNotification']['payment_status'] == 'Completed') { //New subscription or payment ... } elseif ($transactionType == 'subscr-cancel' || $transactionType == 'subscr-eot') { //Subscription cancellation or other problem ... } return $this-&gt;saveAll($data); } ... } ?&gt; </code></pre> <p>You would call from controller using <code>$this-&gt;PaypalNotification-&gt;PremiumSite-&gt;enable(...);</code> but we aren't going to do that, so let's mix it all together...</p> <p><strong>Lesson 5</strong>: Datasources are cool</p> <p>Abstract your PayPal IPN interactions into a datasource which is used by a model.</p> <p>Configuration goes in <code>app/config/database.php</code></p> <pre><code>&lt;?php class DATABASE_CONFIG { ... var $paypal = array( 'datasource' =&gt; 'paypal_ipn', 'sandbox' =&gt; true, 'api_key' =&gt; 'w0u1dnty0ul1k3t0kn0w', } ... } ?&gt; </code></pre> <p>Datasource deals with web service requests (<code>app/models/datasources/paypal_ipn_source.php</code>)</p> <pre><code>&lt;?php class PaypalIpnSource extends DataSource { ... var $endpoint = 'http://www.paypal.com/'; var $Http = null; var $_baseConfig = array( 'sandbox' =&gt; true, 'api_key' =&gt; null, ); function _construct() { if (!$this-&gt;config['api_key']) { trigger_error('No API key specified'); } if ($this-&gt;config['sandbox']) { $this-&gt;endpoint = 'http://www.sandbox.paypal.com/'; } $this-&gt;Http = App::import('Core', 'HttpSocket'); // use HttpSocket utility lib } function validate($data) { ... $reponse = $this-&gt;Http-&gt;post($this-&gt;endpoint, $data); .. return $valid; // boolean } ... } ?&gt; </code></pre> <p>Let the model do the work (<code>app/models/paypal_notification.php</code>)</p> <p>Notifications are only saved if they are valid, sites are only enabled if the notification is saved </p> <pre><code>&lt;?php class PaypalNotification extends AppModel { ... function beforeSave() { $valid = $this-&gt;validate($this-&gt;data); if (!$valid) { return false; } //Minor change to use item_id as premium_site_id $this-&gt;data['PaypalNotification']['premium_site_id'] = $this-&gt;data['PaypalNotification']['item_number']; /* $this-&gt;data['PaypalNotification'] = am($this-&gt;data, // use shorthand functions array('premium_site_id' =&gt; $this-&gt;data['item_number'])); */ return true; } ... function afterSave() { return $this-&gt;PremiumSite-&gt;enable($this-&gt;id); } ... function validate($data) { $paypal = ConnectionManager::getDataSource('paypal'); return $paypal-&gt;validate($data); } ... ?&gt; </code></pre> <p>Controllers are dumb. (<code>app/controllers/paypal_notifications_controller.php</code>)</p> <p>"Are you a post? No? .. then I don't even exist." Now this action just shouts, "I save posted PayPal notifications!"</p> <pre><code>&lt;?php class PaypalNotificationsController extends AppModel { ... var $components = array('RequestHandler', ...); ... function callback() { if (!$this-&gt;RequestHandler-&gt;isPost()) { // use RequestHandler component $this-&gt;cakeError('error404'); } $processed = $this-&gt;PaypalNotification-&gt;save($notification); if (!$processed) { $this-&gt;cakeError('paypal_error'); } } ... } ?&gt; </code></pre> <p><strong>Bonus Round</strong>: Use provided libraries instead of native PHP</p> <p>Refer to previous lessons for examples of the following:</p> <ul> <li><code>String</code> instead of <code>sprintf</code></li> <li><code>HttpSocket</code> instead of <code>fsock</code> functions</li> <li><code>RequestHandler</code> instead of manual checks</li> <li><code>am</code> instead of <code>array_merge</code></li> </ul> <p>These can prevent coding errors, reduce amount of code and/or increase readability.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload