我已经使用CakePHP几个星期了,这是一次很棒的体验。我设法以惊人的速度快速移植了一个网站,我甚至添加了一些我计划但从未实现过的新功能。
查看以下两个控制器,它们允许用户将优质状态添加到链接到其帐户的其中一个站点。他们感觉不是很'蛋糕',他们能以任何方式得到改善吗?
PremiumSites控制器处理注册过程,最终会有其他相关内容,例如历史记录。
class PremiumSitesController extends AppController {
var $name = 'PremiumSites';
function index() {
$cost = 5;
//TODO: Add no site check
if (!empty($this->data)) {
if($this->data['PremiumSite']['type'] == "1") {
$length = (int) $this->data['PremiumSite']['length'];
$length++;
$this->data['PremiumSite']['upfront_weeks'] = $length;
$this->data['PremiumSite']['upfront_expiration'] = date('Y-m-d H:i:s', strtotime(sprintf('+%s weeks', $length)));
$this->data['PremiumSite']['cost'] = $cost * $length;
} else {
$this->data['PremiumSite']['cost'] = $cost;
}
$this->PremiumSite->create();
if ($this->PremiumSite->save($this->data)) {
$this->redirect(array('controller' => 'paypal_notifications', 'action' => 'send', $this->PremiumSite->getLastInsertID()));
} else {
$this->Session->setFlash('Please fix the problems below', true, array('class' => 'error'));
}
}
$this->set('sites',$this->PremiumSite->Site->find('list',array('conditions' => array('User.id' => $this->Auth->user('id'), 'Site.is_deleted' => 0), 'recursive' => 0)));
}
}
class PremiumSitesController extends AppController {
var $name = 'PremiumSites';
function index() {
$cost = 5;
//TODO: Add no site check
if (!empty($this->data)) {
if($this->data['PremiumSite']['type'] == "1") {
$length = (int) $this->data['PremiumSite']['length'];
$length++;
$this->data['PremiumSite']['upfront_weeks'] = $length;
$this->data['PremiumSite']['upfront_expiration'] = date('Y-m-d H:i:s', strtotime(sprintf('+%s weeks', $length)));
$this->data['PremiumSite']['cost'] = $cost * $length;
} else {
$this->data['PremiumSite']['cost'] = $cost;
}
$this->PremiumSite->create();
if ($this->PremiumSite->save($this->data)) {
$this->redirect(array('controller' => 'paypal_notifications', 'action' => 'send', $this->PremiumSite->getLastInsertID()));
} else {
$this->Session->setFlash('Please fix the problems below', true, array('class' => 'error'));
}
}
$this->set('sites',$this->PremiumSite->Site->find('list',array('conditions' => array('User.id' => $this->Auth->user('id'), 'Site.is_deleted' => 0), 'recursive' => 0)));
}
}
PaypalNotifications控制器处理与Paypal的交互。
class PaypalNotificationsController extends AppController {
var $name = 'PaypalNotifications';
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('process');
}
/**
* Compiles premium info and send the user to Paypal
*
* @param integer $premiumID an id from PremiumSite
* @return null
*/
function send($premiumID) {
if(empty($premiumID)) {
$this->Session->setFlash('There was a problem, please try again.', true, array('class' => 'error'));
$this->redirect(array('controller' => 'premium_sites', 'action' => 'index'));
}
$data = $this->PaypalNotification->PremiumSite->find('first', array('conditions' => array('PremiumSite.id' => $premiumID), 'recursive' => 0));
if($data['PremiumSite']['type'] == '0') {
//Subscription
$paypalData = array(
'cmd' => '_xclick-subscriptions',
'business'=> '',
'notify_url' => '',
'return' => '',
'cancel_return' => '',
'item_name' => '',
'item_number' => $premiumID,
'currency_code' => 'USD',
'no_note' => '1',
'no_shipping' => '1',
'a3' => $data['PremiumSite']['cost'],
'p3' => '1',
't3' => 'W',
'src' => '1',
'sra' => '1'
);
if($data['Site']['is_premium_used'] == '0') {
//Apply two week trial if unused
$trialData = array(
'a1' => '0',
'p1' => '2',
't1' => 'W',
);
$paypalData = array_merge($paypalData, $trialData);
}
} else {
//Upfront payment
$paypalData = array(
'cmd' => '_xclick',
'business'=> '',
'notify_url' => '',
'return' => '',
'cancel_return' => '',
'item_name' => '',
'item_number' => $premiumID,
'currency_code' => 'USD',
'no_note' => '1',
'no_shipping' => '1',
'amount' => $data['PremiumSite']['cost'],
);
}
$this->layout = null;
$this->set('data', $paypalData);
}
/**
* IPN Callback from Paypal. Validates data, inserts it
* into the db and triggers __processTransaction()
*
* @return null
*/
function process() {
//Original code from http://www.studiocanaria.com/articles/paypal_ipn_controller_for_cakephp
//Have we been sent an IPN here...
if (!empty($_POST)) {
//...we have so add 'cmd' 'notify-validate' to a transaction variable
$transaction = 'cmd=_notify-validate';
//and add everything paypal has sent to the transaction
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$transaction .= "&$key=$value";
}
//create headers for post back
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($transaction) . "\r\n\r\n";
//If this is a sandbox transaction then 'test_ipn' will be set to '1'
if (isset($_POST['test_ipn'])) {
$server = 'www.sandbox.paypal.com';
} else {
$server = 'www.paypal.com';
}
//and post the transaction back for validation
$fp = fsockopen('ssl://' . $server, 443, $errno, $errstr, 30);
//Check we got a connection and response...
if (!$fp) {
//...didn't get a response so log error in error logs
$this->log('HTTP Error in PaypalNotifications::process while posting back to PayPal: Transaction=' .
$transaction);
} else {
//...got a response, so we'll through the response looking for VERIFIED or INVALID
fputs($fp, $header . $transaction);
while (!feof($fp)) {
$response = fgets($fp, 1024);
if (strcmp($response, "VERIFIED") == 0) {
//The response is VERIFIED so format the $_POST for processing
$notification = array();
//Minor change to use item_id as premium_site_id
$notification['PaypalNotification'] = array_merge($_POST, array('premium_site_id' => $_POST['item_number']));
$this->PaypalNotification->save($notification);
$this->__processTransaction($this->PaypalNotification->id);
} else
if (strcmp($response, "INVALID") == 0) {
//The response is INVALID so log it for investigation
$this->log('Found Invalid:' . $transaction);
}
}
fclose($fp);
}
}
//Redirect
$this->redirect('/');
}
/**
* Enables premium site after payment
*
* @param integer $id uses id from PaypalNotification
* @return null
*/
function __processTransaction($id) {
$transaction = $this->PaypalNotification->find('first', array('conditions' => array('PaypalNotification.id' => $id), 'recursive' => 0));
$txn_type = $transaction['PaypalNotification']['txn_type'];
if($txn_type == 'subscr_signup' || $transaction['PaypalNotification']['payment_status'] == 'Completed') {
//New subscription or payment
$data = array(
'PremiumSite' => array(
'id' => $transaction['PremiumSite']['id'],
'is_active' => '1',
'is_paid' => '1'
),
'Site' => array(
'id' => $transaction['PremiumSite']['site_id'],
'is_premium' => '1'
)
);
//Mark trial used only on subscriptions
if($txn_type == 'subscr_signup') $data['Site']['is_premium_used'] = '1';
$this->PaypalNotification->PremiumSite->saveAll($data);
} elseif($txn_type == 'subscr-cancel' || $txn_type == 'subscr-eot') {
//Subscription cancellation or other problem
$data = array(
'PremiumSite' => array(
'id' => $transaction['PremiumSite']['id'],
'is_active' => '0',
),
'Site' => array(
'id' => $transaction['PremiumSite']['site_id'],
'is_premium' => '0'
)
);
$this->PaypalNotification->PremiumSite->saveAll($data);
}
}
/**
* Used for testing
*
* @return null
*/
function index() {
$this->__processTransaction('3');
}
}
/views/paypal_notifications/send.ctp
将用户发送到Paypal以及所有必要的数据
class PaypalNotificationsController extends AppController {
var $name = 'PaypalNotifications';
function beforeFilter() {
parent::beforeFilter();
$this->Auth->allow('process');
}
/**
* Compiles premium info and send the user to Paypal
*
* @param integer $premiumID an id from PremiumSite
* @return null
*/
function send($premiumID) {
if(empty($premiumID)) {
$this->Session->setFlash('There was a problem, please try again.', true, array('class' => 'error'));
$this->redirect(array('controller' => 'premium_sites', 'action' => 'index'));
}
$data = $this->PaypalNotification->PremiumSite->find('first', array('conditions' => array('PremiumSite.id' => $premiumID), 'recursive' => 0));
if($data['PremiumSite']['type'] == '0') {
//Subscription
$paypalData = array(
'cmd' => '_xclick-subscriptions',
'business'=> '',
'notify_url' => '',
'return' => '',
'cancel_return' => '',
'item_name' => '',
'item_number' => $premiumID,
'currency_code' => 'USD',
'no_note' => '1',
'no_shipping' => '1',
'a3' => $data['PremiumSite']['cost'],
'p3' => '1',
't3' => 'W',
'src' => '1',
'sra' => '1'
);
if($data['Site']['is_premium_used'] == '0') {
//Apply two week trial if unused
$trialData = array(
'a1' => '0',
'p1' => '2',
't1' => 'W',
);
$paypalData = array_merge($paypalData, $trialData);
}
} else {
//Upfront payment
$paypalData = array(
'cmd' => '_xclick',
'business'=> '',
'notify_url' => '',
'return' => '',
'cancel_return' => '',
'item_name' => '',
'item_number' => $premiumID,
'currency_code' => 'USD',
'no_note' => '1',
'no_shipping' => '1',
'amount' => $data['PremiumSite']['cost'],
);
}
$this->layout = null;
$this->set('data', $paypalData);
}
/**
* IPN Callback from Paypal. Validates data, inserts it
* into the db and triggers __processTransaction()
*
* @return null
*/
function process() {
//Original code from http://www.studiocanaria.com/articles/paypal_ipn_controller_for_cakephp
//Have we been sent an IPN here...
if (!empty($_POST)) {
//...we have so add 'cmd' 'notify-validate' to a transaction variable
$transaction = 'cmd=_notify-validate';
//and add everything paypal has sent to the transaction
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$transaction .= "&$key=$value";
}
//create headers for post back
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($transaction) . "\r\n\r\n";
//If this is a sandbox transaction then 'test_ipn' will be set to '1'
if (isset($_POST['test_ipn'])) {
$server = 'www.sandbox.paypal.com';
} else {
$server = 'www.paypal.com';
}
//and post the transaction back for validation
$fp = fsockopen('ssl://' . $server, 443, $errno, $errstr, 30);
//Check we got a connection and response...
if (!$fp) {
//...didn't get a response so log error in error logs
$this->log('HTTP Error in PaypalNotifications::process while posting back to PayPal: Transaction=' .
$transaction);
} else {
//...got a response, so we'll through the response looking for VERIFIED or INVALID
fputs($fp, $header . $transaction);
while (!feof($fp)) {
$response = fgets($fp, 1024);
if (strcmp($response, "VERIFIED") == 0) {
//The response is VERIFIED so format the $_POST for processing
$notification = array();
//Minor change to use item_id as premium_site_id
$notification['PaypalNotification'] = array_merge($_POST, array('premium_site_id' => $_POST['item_number']));
$this->PaypalNotification->save($notification);
$this->__processTransaction($this->PaypalNotification->id);
} else
if (strcmp($response, "INVALID") == 0) {
//The response is INVALID so log it for investigation
$this->log('Found Invalid:' . $transaction);
}
}
fclose($fp);
}
}
//Redirect
$this->redirect('/');
}
/**
* Enables premium site after payment
*
* @param integer $id uses id from PaypalNotification
* @return null
*/
function __processTransaction($id) {
$transaction = $this->PaypalNotification->find('first', array('conditions' => array('PaypalNotification.id' => $id), 'recursive' => 0));
$txn_type = $transaction['PaypalNotification']['txn_type'];
if($txn_type == 'subscr_signup' || $transaction['PaypalNotification']['payment_status'] == 'Completed') {
//New subscription or payment
$data = array(
'PremiumSite' => array(
'id' => $transaction['PremiumSite']['id'],
'is_active' => '1',
'is_paid' => '1'
),
'Site' => array(
'id' => $transaction['PremiumSite']['site_id'],
'is_premium' => '1'
)
);
//Mark trial used only on subscriptions
if($txn_type == 'subscr_signup') $data['Site']['is_premium_used'] = '1';
$this->PaypalNotification->PremiumSite->saveAll($data);
} elseif($txn_type == 'subscr-cancel' || $txn_type == 'subscr-eot') {
//Subscription cancellation or other problem
$data = array(
'PremiumSite' => array(
'id' => $transaction['PremiumSite']['id'],
'is_active' => '0',
),
'Site' => array(
'id' => $transaction['PremiumSite']['site_id'],
'is_premium' => '0'
)
);
$this->PaypalNotification->PremiumSite->saveAll($data);
}
}
/**
* Used for testing
*
* @return null
*/
function index() {
$this->__processTransaction('3');
}
}
答案 0 :(得分:28)
第1课:不要使用PHP的超级全局
$_POST
= $this->params['form'];
$_GET
= $this->params['url'];
$_GLOBALS
= Configure::write('App.category.variable', 'value');
$_SESSION
(查看)= $session->read();
(帮助者)$_SESSION
(控制器)= $this->Session->read();
(组件)$_SESSION['Auth']['User']
= $this->Auth->user();
$_POST
的替换:
<?php
...
//foreach ($_POST as $key => $value) {
foreach ($this->params['form'] as $key => $value) {
...
//if (isset($_POST['test_ipn'])) {
if (isset($this->params['form']['test_ipn'])) {
...
?>
第2课:视图用于分享(与用户共享)
代码记录“编译高级信息并将用户发送到Paypal”不会将用户发送到PayPal。你在视图中重定向吗?
<?php
function redirect($premiumId) {
...
$this->redirect($url . '?' . http_build_query($paypalData), 303);
}
在控制器末尾重定向并删除视图。 :)
第3课:数据操作属于模型层
<?php
class PremiumSite extends AppModel {
...
function beforeSave() {
if ($this->data['PremiumSite']['type'] == "1") {
$cost = Configure::read('App.costs.premium');
$numberOfWeeks = ((int) $this->data['PremiumSite']['length']) + 1;
$timestring = String::insert('+:number weeks', array(
'number' => $numberOfWeeks,
));
$expiration = date('Y-m-d H:i:s', strtotime($timestring));
$this->data['PremiumSite']['upfront_weeks'] = $weeks;
$this->data['PremiumSite']['upfront_expiration'] = $expiration;
$this->data['PremiumSite']['cost'] = $cost * $numberOfWeeks;
} else {
$this->data['PremiumSite']['cost'] = $cost;
}
return true;
}
...
}
?>
第4课:模型不只是用于数据库访问
将代码记录为“付款后启用高级网站”移至PremiumSite模型,并在付款后拨打电话:
<?php
class PremiumSite extends AppModel {
...
function enable($id) {
$transaction = $this->find('first', array(
'conditions' => array('PaypalNotification.id' => $id),
'recursive' => 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->saveAll($data);
}
...
}
?>
您可以使用$this->PaypalNotification->PremiumSite->enable(...);
从控制器拨打电话,但我们不打算这样做,所以让我们将它们混合在一起......
第5课:数据源很酷
将您的PayPal IPN交互抽象为模型使用的数据源。
配置进入app/config/database.php
<?php
class DATABASE_CONFIG {
...
var $paypal = array(
'datasource' => 'paypal_ipn',
'sandbox' => true,
'api_key' => 'w0u1dnty0ul1k3t0kn0w',
}
...
}
?>
数据源处理Web服务请求(app/models/datasources/paypal_ipn_source.php
)
<?php
class PaypalIpnSource extends DataSource {
...
var $endpoint = 'http://www.paypal.com/';
var $Http = null;
var $_baseConfig = array(
'sandbox' => true,
'api_key' => null,
);
function _construct() {
if (!$this->config['api_key']) {
trigger_error('No API key specified');
}
if ($this->config['sandbox']) {
$this->endpoint = 'http://www.sandbox.paypal.com/';
}
$this->Http = App::import('Core', 'HttpSocket'); // use HttpSocket utility lib
}
function validate($data) {
...
$reponse = $this->Http->post($this->endpoint, $data);
..
return $valid; // boolean
}
...
}
?>
让模型完成工作(app/models/paypal_notification.php
)
通知只有在有效时才会保存,只有在保存通知时才启用网站
<?php
class PaypalNotification extends AppModel {
...
function beforeSave() {
$valid = $this->validate($this->data);
if (!$valid) {
return false;
}
//Minor change to use item_id as premium_site_id
$this->data['PaypalNotification']['premium_site_id'] =
$this->data['PaypalNotification']['item_number'];
/*
$this->data['PaypalNotification'] = am($this->data, // use shorthand functions
array('premium_site_id' => $this->data['item_number']));
*/
return true;
}
...
function afterSave() {
return $this->PremiumSite->enable($this->id);
}
...
function validate($data) {
$paypal = ConnectionManager::getDataSource('paypal');
return $paypal->validate($data);
}
...
?>
控制器很笨。 (app/controllers/paypal_notifications_controller.php
)
“你是一个帖子吗?不是吗?那我甚至都不存在。”现在这个动作只是喊道,“我保存了发布的PayPal通知!”
<?php
class PaypalNotificationsController extends AppModel {
...
var $components = array('RequestHandler', ...);
...
function callback() {
if (!$this->RequestHandler->isPost()) { // use RequestHandler component
$this->cakeError('error404');
}
$processed = $this->PaypalNotification->save($notification);
if (!$processed) {
$this->cakeError('paypal_error');
}
}
...
}
?>
奖金回合:使用提供的库而不是本机PHP
有关以下内容的示例,请参阅之前的课程:
String
代替sprintf
HttpSocket
代替fsock
个功能RequestHandler
而非人工检查am
代替array_merge
这些可以防止编码错误,减少代码量和/或提高可读性。
答案 1 :(得分:5)
除了deizel指出的所有内容(很棒的帖子顺便提一下),请记住一个基本的蛋糕原则:胖模特,瘦小的控制器。您可以查看this example,但基本的想法是将所有数据修改的内容放入模型中。您的控制器(大多数)应该只是模型和视图之间的链接。你的PremiumSitesController :: index()是一个完美的例子,应该在你的模型中的某个地方(正如deizel所指出的那样)。
Chris Hartjes也写了一个book about refactoring,如果你真的想学习它,你可能想看看它(它不是免费的,但它很便宜)。另外,Matt Curry有一个名字很酷:Super Awesome Advanced CakePHP Tips,而且完全免费下载。两者都是一个很好的阅读。
我还想插一篇关于蛋糕的文章,我认为这对蛋糕中的代码质量非常重要:Code formatting and readability。虽然我明白人们是否不同意......: - )
答案 2 :(得分:1)
我会指出这两件事:
$cost
变量,或$ paypalData ...你可以从其他地方获得它(例如flash应该来自语言文件),但不要将配置与实现混合......这将使类更易读,维护更容易...... < / LI>
这就是我认为的蛋糕......
格尔茨
back2dos