Upload
pierre-martin
View
5.604
Download
1
Tags:
Embed Size (px)
DESCRIPTION
Using and reusing plugins across CakePHP applications - CakeFest 2010 (Chicago)
Citation preview
PLUGINSAcross applications
Using and Reusing
CakeFest 2010 - Chicago Pierre MARTIN
ME
@pierremartin http://pierre-martin.fr
June 2008 - CakePHP 1.2 beta
CakePHP-fr
YOU
?Used a plugin
Wrote a plugin
Reuse regularly
WHY
Spaghetti code
Libraries
OOP, TemplatesMVC and Frameworks
+ Reusable classes (Behaviors, Components, Helpers)
Fat ModelsSkinny Controllers
REUSING CODE
Plugins
Or
C P S R
Copy and Paste / Search and Replace :)
HOW
/APP/PLUGINSmy_plugin/ my_plugin_app_model.php my_plugin_app_controller.php models/ behaviors/ my_plugin_foo.php my_plugin_bar.php controllers/ components/ my_plugin_foos_controller.php my_plugin_bars_controller.php views/ helpers/ layouts/ elements/ my_plugin_foos/ index.ctp my_plugin_bars/ add.ctp
locale/ eng/ LC_MESSAGES/ my_plugin.po (__d())webroot/ css/
style.cssother.css
img/logo.png
js/foobar.js
tests/libs/vendors/
MODELS/app/plugins/users/models/user.php
ClassRegistry::init(‘Users.User’);
App::import(‘Model’, ‘Users.User’);
public $belongsTo = array(‘Users.User’);public $belongsTo = array(
‘User’ => array(‘className’ => ‘Users.User’));
public $belongsTo = array(‘User’ => array(
‘className’ => ‘Users.UsersUser’));
PLUGIN.THING
It works for everything!
// Behaviorspublic $actsAs = array(‘Comments.Commentable’);
// Componentspublic $components = array(‘Twitter.TwitterAuth’);
// Helperspublic $helpers = array(‘Tags.TagCloud’);
// Libraries, Vendors, Custom routes...App::import(‘Lib’, ‘Chuck.Norris’);
ACTIONS / ELEMENTS/app/plugins/users/controllers/users_controller.php
/app/plugins/users/views/elements/login.ctp
$this->redirect(‘plugin’ => ‘users’, ‘controller’ => ‘users’, ‘action’ => ‘register’);$this->redirect(‘plugin’ => null, ‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘home’);
$this->Html->link(‘plugin’ => ‘users’, ‘controller’ => ‘users’, ‘action’ => ‘index’);// Will generate http://domain.com/users
$this->element(‘login’, array(‘plugin’ => ‘users’, ‘foo’ => ‘bar’));
ASSETS/app/plugins/jquery/webroot/js/jquery.js
/app/plugins/jquery/webroot/css/jquery.ui.css
/app/plugins/jquery/webroot/img/subdir/logo.png
$this->Html->script(‘/jquery/js/jquery.js’);
$this->Html->css(‘/jquery/css/jquery.ui.css’);
$this->Html->image(‘/jquery/img/subdir/logo.png’);
Source: @dogmatic69
EXTENDING PLUGINS
WHY?
Appearance customization
App specific logic
Changing features, redirections...
Adding features
“USERS” PLUGIN
• User model
• id, username, password
• Users Controller
• login, logout, register, reset_password
• Views
VIEWS/app/plugins/users/views/users/register.ctp/app/views/plugins/users/users/register.ctp
<h1><?php __(‘Create a new account on Awesomeness.org’); ?><?php
echo $this->Form->create(‘User’);echo $this->Form->input(‘username’);echo $this->Form->input(‘password’);echo $this->Form->input(‘password_confirm’);// App specific featureecho $this->Form->input(‘Profile.newsletter’, array(
‘label’ => __(‘Suscribe to our newsletter’, true),‘type’ => ‘checkbox’));
echo $this->Form->end(__(‘I want to be awesome!’, true));?>
MODELS<?phpApp::import(‘Model’, ‘Users.User’);class MyUser extends User {
// [...]public $hasOne = array(‘Profile’);// [...]public function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);$this->validate[‘username’][‘length’] = array(
‘rule’ => array(‘minLength’, 5));}// [...]public function register($data) {
$success = parent::register($data);if ($success) {
// Your business logic here}return $success;
}// [...]public function foobar() { }
}?>
CONTROLLERS<?phpApp::import(‘Controller’, ‘Users.Users’);class MyUsersController extends UsersController {
// [...]public function beforeFilter() {
$this->User = ClassRegistry::init('MyUser');parent::beforeFilter();$this->Auth->deny('index');
}// [...]public function register() {
if (!empty($this->data)) {if ($this->User->register($this->data)) {
// Your app specific logic here$this->redirect(‘controller’ => ‘pages’, ‘action’ => ‘display’, ‘welcome’);
}}parent::register();
}// [...]public function foobar() { }
}?>
CONTROLLERSRouter::connect(
'/users/:action/*',array('plugin' => ‘users’, 'controller' => 'users'));
Router ::connect('/users/:action/*',array('plugin' => null, 'controller' => 'my_users'));
public function render($action = null, $layout = null, $file = null) {if (is_null($action)) {
$action = $this->action;}if ($action !== false) {
if (!file_exists(VIEWS . 'my_users' . DS . $action . '.ctp')) { $file = App::pluginPath('users') . 'views' . DS . 'users' . DS . $action . '.ctp'; }
}return parent::render($action, $layout, $file);
}
TODO Improve me
... AND IT WORKS WITH EVERYTHING
App::import(‘Behavior’, ‘Comments.Commentable’);class MyCommentable extends Commentable {
}
Helpers, Libraries, Components, Behaviors...
TIPS AND TRICKS
Serious stuff coming!
DON’T TRUST ME!
Unless you’ve tried it yourself
REUSE EXISTING PLUGINS
CakePackages.com:•548 CakePHP related projects•284 developers
CakePHP’s main feature is its community
KISS
ExtendRefactor
USE OBJECTS ATTRIBUTES// Models$this->alias$this->name$this->displayField$this->primaryKey
$this->data[‘User’][‘id’]; // Before$this->data[$this->alias][$this->primaryKey]; // After
// Controllers$this->plugin$this->modelClass // MyModel$this->modelKey // my_model$this->name
Add attributes to your classes!
COMPONENTS ARE THE KEY!
Add some magic in your plugins
HELPER AUTOLOADING
class CommentManager extends Object {public $autoHelper = true;
public $helperName = ‘Comments.CommentWidget’;
public function beforeRender(Controller $Controller) {if ($this->autoHelper) {
$Controller->helpers[] = $helperName;}
}}
BEHAVIOR AUTOLOADING
class CommentManager extends Object {public $autoBehavior = true;
public $behaviorName = ‘Comments.Commentable’;
public function startup(Controller $Controller) {$Model = $Controller->{$Controller->modelClass};if ($autoBehavior && !$Model->Behaviors->attached($this->behaviorName)) { $Model->Behaviors->attach($this->behaviorName);}
}}
AUTODETECTED ACTIONS
class CommentManager extends Object {public $autoActions = true;
public function startup(Controller $Controller) {if ($autoActions) {
if (!empty($Controller->data[‘Comment’])) {// [...] Automatically save the comment$Controller->redirect($Controller->referer());
}}
}}
AUTO DATA FETCHING
class FoobarManager extends Object {
public function beforeRender(Controller $Controller) {$data = [...]; // Your logic here to get the correct data for the view$Controller->set(‘data_for_foobar_helper’, $data);
}
}
HELPERS THAT HELP
• Reduce PHP code in views
• Unique entry point
• Deal with elements
• Performance optimization
... BEHIND THE SCENE
class FoobarHelper extends AppHelper {
public function beforeRender() { if (ClassRegistry::isKeySet('view')) { $View = ClassRegistry::getObject('view'); $this->_data = $View->getVar('data_for_foobar_helper');
}}
}
public function display($element = 'carts/view', $options) { if (!ClassRegistry::isKeySet('view')) { return; }
if (empty($cartData)) { if (is_a($this->Session, 'SessionHelper') && $this->Session->check('Cart')) { $cartData = $this->Session->read('Cart'); } else { $cartData = $this->requestAction($this->cartRequestUrl); } }
if (empty($cartData)) { trigger_error(__d('cart', 'No cart found.', true), E_USER_NOTICE); } else { // [...] Format the data and add default options (caching...) $options['cartData'] = $cartData; return ClassRegistry::getObject('view')->element($element, $options); }}
USE THE CONFIGURE CLASS•With default values
• Configure::load()
public function __construct($id = false, $table = null, $ds = null) { $userClass = Configure::read('App.UserClass'); if (empty($userClass)) { $userClass = 'User'; } $this->belongsTo['User'] = array( 'className' => $userClass, 'foreignKey' => 'user_id');
// [...]}
CALLBACKS / HOOKS
class StuffableBehavior extends ModelBehavior {
public function doStuff(Model $Model, $id) {if ($Model->isStuffable($id)) {
// [...]if (method_exists($Model, ‘afterStuff’)) {
$Model->afterStuff();}
}}
// Fallback, default logicpublic function isStuffable(Model $Model, $id) {
return true;}
}
HIGHLIGHT ERRORSTrigger errors for the developer
Throw Exceptions for the User
$mandatory = Configure::read('Foo.bar');if (empty($mandatory)) {
trigger_error(‘You must configure your Foobar’, E_USER_ERROR);}
public function doStuff($id) {$Model->id = $id;if (!$Model->exists($id)) {
throw new OutOfBoundsException(__(‘Invalid object’, true));}
}
MAKE MIGRATIONS EASY• Version your code, tag versions (KISS, Extend, Refactor)
•Document API changes between versions
• Use CakeDC’s awesome Migrations plugin!
• Schema updates
• Initial data
• Configuration assistance
... AND ALSO•Write tests
• Use __d(‘myplugin’, ‘This is my text’);
•Document your code
• Provide interfaces to implement (Lib)
•Write tests... really!
WHAT IS MISSING?
NAMESPACES
MyPluginFoobarsController
ClassRegistry downsides
PLUGINS DIRECTORY
CakePackages.com
Reduce plugin duplication
“Diversity is good... with moderation”
PLUGIN MANAGER
Generic installer
Shell
Migrations
METADATA
Implemented “Interface”
Dependencies
Version
QUESTIONS?
@pierremartin http://pierre-martin.fr