16. “USERS” PLUGIN
• User model
• id, username, password
• Users Controller
• login, logout, register, reset_password
• Views
17. 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 feature
echo $this->Form->input(‘Profile.newsletter’, array(
‘label’ => __(‘Suscribe to our newsletter’, true),
‘type’ => ‘checkbox’));
echo $this->Form->end(__(‘I want to be awesome!’, true));
?>
18. MODELS
<?php
App::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() { }
}
?>
19. CONTROLLERS
<?php
App::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() { }
}
?>
20. TO
DO
I mp
CONTROLLERS rov
em
e
Router::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);
}
21. ... AND IT WORKS WITH
EVERYTHING
Helpers, Libraries, Components, Behaviors...
App::import(‘Behavior’, ‘Comments.Commentable’);
class MyCommentable extends Commentable {
}
28. HELPER AUTOLOADING
class CommentManager extends Object {
public $autoHelper = true;
public $helperName = ‘Comments.CommentWidget’;
public function beforeRender(Controller $Controller) {
if ($this->autoHelper) {
$Controller->helpers[] = $helperName;
}
}
}
29. 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);
}
}
}
30. 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());
}
}
}
}
31. 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);
}
}
32. HELPERS THAT HELP
• Reduce PHP code in views
• Unique entry point
• Deal with elements
• Performance optimization
33. ... 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');
}
}
}
34. 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);
}
}
35. 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');
// [...]
}
36. 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 logic
public function isStuffable(Model $Model, $id) {
return true;
}
}
37. HIGHLIGHT ERRORS
Trigger 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));
}
}
38. 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
39. ... AND ALSO
• Write tests
• Use __d(‘myplugin’, ‘This is my text’);
• Document your code
• Provide interfaces to implement (Lib)
• Write tests... really!
Users is something that is common to most of the applications
There are common features, but User data / associations / workflow is different across applications
Here is a quick and dirty render overriding allowing you to load parents views if needed
I will introduce some practices I find useful and I learnt when trying to reuse plugins I wrote
This is just something personal... so take it as it!
The keywork here is &#x201C;magic&#x201D;.
You can make your plugin easier to integrate in an application by adding some automagical features in a Component attached to Controller that needs it.
However, be sure to provide a way to disable this magic if needed.
CakePHP-fr now: 15000 messages, 800 members, 500 visitors a day