CakePHP Workshop
Build fast, grow solid.
Walther Lalk
CakePHP core team member
Croogo core team member
Lead Software Developer at
Troop Scouter at 9th Pretoria
(Irene) Air Scouts
Husband
Development environment
VirtualBox withVagrant
Recommended vagrant box is the FriendsOfCake
vagrant box.
Download it by running
$ git clone https://github.com/FriendsOfCake/vagrant-chef.git
Then start the vagrant box up with
$ cd vagrant-chef
$ vagrant up
$ vagrant ssh (For windows you’ll probably need
to use Putty)
Add this to your hosts file
192.168.13.37 app.dev
Linux or OSX : /etc/hosts
Windows:
C:WindowsSystem32driversetchosts
PHP built in server
Ensure you have PHP 5.5.9+ with the Intl and
Mbstring and a database server running (with
the corresponding PDO extension).
Supported:
MySQL,
PostgreSQL,
SQLite,
SQL Server
I will be using this with SQLite.
Following along at home
https://github.com/dakota/phpsa2016-complete
Installing CakePHP
Run
$ composer create-project cakephp/app app
Then
$ cd app
If you are using the vagrant
box, then visit http://app.dev in
your browser.
Otherwise, wait for the next slide
PHP Server
Run
$ bin/cake server
Visit http://localhost:8765/
Getting baked
Database configuration
If you’re using the Vagrant box, and have a green tick for database, then you’re good to go.
PHP Server users probably need to do some config. Recommend simply using SQLite (It’s the easiest
to get going).
Open config/app.php
Find the Datasources, replace the default datasource with
'default' => [
'className' => 'CakeDatabaseConnection',
'driver' => 'CakeDatabaseDriverSqlite',
'database' => ROOT . DS . 'database.sqlite',
'encoding' => 'utf8',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
],
Bake a database
$ bin/cake bake migration CreateMembers first_name:string last_name:string email:string
created modified
$ bin/cake bake migration CreateEvents title:string description:text start:datetime
end:datetime created modified
$ bin/cake bake migration CreateEventsMembers event_id:integer:primary
member_id:integer:primary
Bake a database
The migration bake task can sometimes get confused with composite primary keys, and tries to
make each primary key an autoincrement field. We need to fix that!
Open config/Migrations/…_CreateEventsMembers.php and remove the two
‘autoIncrement’ => true, lines.
If you are using SQLite, you need to open all the files in the config/Migrations, and change all
instances of ‘default’ => null to be ‘default’ => ‘’
Seed that database
$ bin/cake bake seed Members
Open config/Seeds/MembersSeed.php, and update the $data variable
$data = [
[
'first_name' => 'Walther',
'last_name' => 'Lalk',
'email' => 'waltherlalk@cakephp.org',
'created' => date('Y-m-d H:i:s'),
'modified' => date('Y-m-d H:i:s'),
]
];
Seed that database
$ bin/cake bake seed Events
Open config/Seeds/EventsSeed.php, and update the $data variable
$data = [
[
'title' => 'PHP South Africa 2016',
'description' => '',
'start' => '2016-09-28 08:00',
'end' => '2016-09-30 17:00',
'created' => date('Y-m-d H:i:s'),
'modified' => date('Y-m-d H:i:s'),
]
];
Running migrations
$ bin/cake migrations migrate
$ bin/cake migrations seed
Bake your application
$ bin/cake bake all Members
$ bin/cake bake all Events
Visit /members in your browser, there should be 1 member. Add another one.
Now visit /events in your browser, there should be 1 event. Add another one.
Notice how both of them have a multi-select box for members or events? We’re going to remove
that at a later stage and change how it works.
Anatomy of a CakePHP
application
Marshalling
Persisting
Validation
Open src/Model/Table/EventsTable.php, find in the validationDefault method.
Change notEmpty() to be allowEmpty()
$validator
->requirePresence('description', 'create')
->notEmpty('description');
Application rules
public function buildRules(RulesChecker $rules)
{
$rules->add(
function (Event $event) {
return $event->start <= $event->end;
},
'endAfterStart',
[
'errorField' => 'end',
'message' => 'The event cannot end before it has started'
]
);
return parent::buildRules($rules);
}
The art of knowing who’s
there
Adding a password
$ bin/cake bake migration AddPasswordToMembers password:string[60]
$ bin/cake migrations migrate
Add to src/Model/Entity/MemberEntity.php
protected function _setPassword($password)
{
if (strlen($password) > 0) {
return (new CakeAuthDefaultPasswordHasher())->hash($password);
}
return $this->password;
}
Adding a password
Add to src/Templates/Members/add.ctp and src/Templates/Members/edit.ctp
echo $this->Form->input('password', [
'value' => ''
]);
echo $this->Form->input('password_confirm', [
'label' => 'Confirm password',
’type' => 'password',
'value' => ''
]);
Adding a password
Add validation to the password, and password confirmation fields.
$validator
->requirePresence('password', 'create')
->notEmpty('password', 'You need to provide a password.', 'create')
->minLength('password', 6, 'Your password must be 6 characters or longer');
$condition = function ($context) {
return !empty($context['data']['password']);
};
$validator
->requirePresence('password_confirm', $condition)
->notEmpty('password_confirm', 'Please confirm your password', $condition)
->add('password_confirm', 'mustMatch', [
'rule' => function ($check, $context) {
return $context['data']['password'] === $check;
},
'on' => $condition,
'message' => 'Password does not match'
]);
Adding a password
Edit your existing members and give them passwords. They will be automatically hashed.
Creating the login action
Open src/Controller/MembersController.php
public function login()
{
if ($this->request->is('post')) {
$member = $this->Auth->identify();
if ($member) {
$this->Auth->setUser($member);
return $this->redirect($this->Auth->redirectUrl());
} else {
$this->Flash->error(__('Email address or password is incorrect'), [
'key' => 'auth'
]);
}
}
}
public function logout()
{
return $this->redirect($this->Auth->logout());
}
Creating the login template
Create src/Templates/Members/login.ctp
<div class="login form large-12 columns content">
<?php
echo $this->Form->create();
echo $this->Form->input('email');
echo $this->Form->input('password');
echo $this->Form->button('Login');
echo $this->Form->end();
?>
</div>
Enabling Authentication
Open src/Controller/AppController.php, and add to the initialize method.
$this->loadComponent('Auth', [
'loginAction' => [
'controller' => 'Members',
'action' => 'login',
],
'authenticate' => [
'Form' => [
'fields' => ['username' => 'email', 'password' => 'password'],
'userModel' => 'Members'
]
]
]);
Try to visit /members now
Event security
We are adding a organiser to our events. Only the organiser is allowed to change the event.
$ bin/cake bake migration AddOrganiserToEvents organiser_id:integer
$ bin/cake migrations migrate
$this->belongsTo('Organiser', [
'foreignKey' => 'organiser_id',
'className' => 'Members'
]);
Open src/Model/Table/EventsTable.php, and add to the initialize method.
Open src/Model/Table/MembersTable.php, and add to the initialize method.
$this->hasMany('OrganisingEvents', [
'foreignKey' => 'organiser_id',
'className' => 'Events'
]);
Event security
Enforce foreign key constraints at an application level by adding the following to the buildRules
method in the EventsTable class.
$rules->existsIn('organiser_id', 'Organiser', 'That member does not exist in the database.');
Who’s the organiser?
The member creating the event should be able to choose who the event organiser is.
In src/Templates/Events/add.ctp
echo $this->Form->input('organiser_id', [
'empty' => '-- Select organiser --',
'options' => $members,
'default' => $this->request->session()->read('Auth.User.id')
]);
In src/Templates/Events/edit.ctp
echo $this->Form->input('organiser_id', [
'empty' => '-- Select organiser --',
'options' => $members,
]);
Who’s that?
Who is organiser 1?
protected $_virtual = [
'full_name'
];
protected function _getFullName()
{
return sprintf('%s %s (%s)', $this->first_name, $this->last_name, $this->email);
}
Add to the Member entity class
In the MembersTable class, change the displayField to ‘full_name’
Better!
Event security
To enforce that an event can only be modified by it’s organiser, open
src/Controller/EventsController.php and add to the edit method, just after the get() call.
if ($event->organiser_id !== $this->Auth->user('id')) {
throw new ForbiddenException();
}
Member security
To prevent a member from editing another member’s profile, simply add
if ($id !== $this->Auth->user('id')) {
throw new ForbiddenException();
}
To the edit method in the MembersController.
Allow registrations
Allow new members to register by adding a beforeFilter method to the MembersController.
public function beforeFilter(CakeEventEvent $event)
{
$this->Auth->allow('add');
return parent::beforeFilter($event);
}
Add make a pretty URL for registration and login. Add to config/routes.php
$routes->connect('/register', ['controller' => 'Members', 'action' => 'add']);
$routes->connect('/login', ['controller' => 'Members', 'action' => 'login']);
I’m going
Belongs to many
Create a join table object
$ bin/cake bake model EventsMembers
Change
'joinTable' => 'events_members'
to
'through' => 'EventsMembers',
'saveStrategy' => CakeORMAssociationBelongsToMany::SAVE_APPEND
in the Members and Events table objects.
Event attendance
Add a field to capture the type of event attendance
$ bin/cake bake migration AddTypeToEventsMembers type:string[10]
$ bin/cake migrations migrate
Add to EventsMember entity
const TYPE_GOING = 'going';
const TYPE_INTERESTED = 'interested';
const TYPE_NOT_GOING = 'notGoing';
Event attendance
In EventsTable, add
public function linkMember(AppModelEntityEvent $event, $memberId, $type)
{
$member = $this->Members->get($memberId);
//Add the join data
$member->_joinData = new AppModelEntityEventsMember([
'type' => $type
]);
return $this->association('Members')->link($event, [$member]);
}
Event attendance
In EventsController, add
public function linkActiveMember($eventId, $type)
{
$event = $this->Events->get($eventId);
if ($this->Events->linkMember($event, $this->Auth->user('id'), $type)) {
$this->Flash->success('Registered!');
} else {
$this->Flash->error('Something went wrong.');
}
return $this->redirect($this->referer());
}
Event attendance
In the Event entity
public function memberStatus($memberId, $type)
{
if (!$this->has('members')) {
return false;
}
$member = collection($this->members)
->firstMatch(['id' => $memberId]);
if (!$member) {
return false;
}
return $member->_joinData->type === $type;
}
Event attendance
In src/Template/Events/index.ctp, in the actions table cell
<br>
<?php
if (!$event->memberStatus($this->request->session()
->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_GOING)
) {
echo $this->Html->link(__('Going'), [
'action' => 'linkActiveMember',
$event->id,
AppModelEntityEventsMember::TYPE_GOING
]);
}
if (!$event->memberStatus($this->request->session()
->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_INTERESTED)
) {
echo $this->Html->link(__('Interested'), [
'action' => 'linkActiveMember',
$event->id,
AppModelEntityEventsMember::TYPE_INTERESTED
]);
}
if (!$event->memberStatus($this->request->session()
->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_NOT_GOING)
) {
echo $this->Html->link(__('Not going'), [
'action' => 'linkActiveMember',
$event->id,
AppModelEntityEventsMember::TYPE_NOT_GOING
]);
}
?>
Event attendance
Open src/Templates/Events/add.ctp and src/Templates/Events/edit.ctp and remove
echo $this->Form->input('members._ids', ['options' => $members]);
Open src/Templates/Members/add.ctp and src/Templates/Members/edit.ctp and remove
echo $this->Form->input('events._ids', ['options' => $events]);
We don’t need code where
we’re going
Installing CRUD
$ composer require friendsofcake/crud
$ bin/cake plugin load Crud
use CrudControllerControllerTrait;
Add the Crud trait to your AppController
And, load the Crud component in the initialize method
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Edit',
'Crud.Add',
'Crud.Delete'
],
'listeners' => [
'Crud.RelatedModels'
]
]);
Remove all your code
Or, at least all index, view, edit, add and delete actions.
It still works!?
Thanks to the power of CRUD, everything still works
(Mostly)
Adding back functionality
Create a AuthListener class in the src/Listener directory
<?php
namespace AppListener;
use CakeEventEvent;
use CakeNetworkExceptionForbiddenException;
use CrudListenerBaseListener;
/**
* Class AuthListener
*/
class AuthListener extends BaseListener
{
/**
* Settings
*
* @var array
*/
protected $_defaultConfig = [
'property' => 'id',
'message' => 'You are not allowed to access the requested resource.',
'actions' => ['edit', 'delete']
];
Adding back functionality
public function afterFind(Event $event)
{
if (!in_array($this->_request()->param('action'), $this->config('actions'))
) {
return;
}
$entity = $event->subject()->entity;
$userId = $this->_controller()->Auth->user('id');
if ($entity->get($this->config('property')) !== $userId) {
throw new ForbiddenException($this->config('message'));
}
}
}
Adding back functionality
MembersController beforeFilter, add:
$this->Crud->addListener('Auth', 'Auth', [
'property' => 'id'
]);
Add a beforeFilter for EventsController
public function beforeFilter(CakeEventEvent $event)
{
$this->Crud->addListener('Auth', 'Auth', [
'property' => 'organiser_id'
]);
return parent::beforeFilter($event);
}
Questions?
Thank You.

CakePHP workshop

  • 1.
  • 2.
    Walther Lalk CakePHP coreteam member Croogo core team member Lead Software Developer at Troop Scouter at 9th Pretoria (Irene) Air Scouts Husband
  • 3.
    Development environment VirtualBox withVagrant Recommendedvagrant box is the FriendsOfCake vagrant box. Download it by running $ git clone https://github.com/FriendsOfCake/vagrant-chef.git Then start the vagrant box up with $ cd vagrant-chef $ vagrant up $ vagrant ssh (For windows you’ll probably need to use Putty) Add this to your hosts file 192.168.13.37 app.dev Linux or OSX : /etc/hosts Windows: C:WindowsSystem32driversetchosts PHP built in server Ensure you have PHP 5.5.9+ with the Intl and Mbstring and a database server running (with the corresponding PDO extension). Supported: MySQL, PostgreSQL, SQLite, SQL Server I will be using this with SQLite.
  • 4.
    Following along athome https://github.com/dakota/phpsa2016-complete
  • 5.
    Installing CakePHP Run $ composercreate-project cakephp/app app Then $ cd app
  • 6.
    If you areusing the vagrant box, then visit http://app.dev in your browser. Otherwise, wait for the next slide
  • 7.
    PHP Server Run $ bin/cakeserver Visit http://localhost:8765/
  • 8.
  • 9.
    Database configuration If you’reusing the Vagrant box, and have a green tick for database, then you’re good to go. PHP Server users probably need to do some config. Recommend simply using SQLite (It’s the easiest to get going). Open config/app.php Find the Datasources, replace the default datasource with 'default' => [ 'className' => 'CakeDatabaseConnection', 'driver' => 'CakeDatabaseDriverSqlite', 'database' => ROOT . DS . 'database.sqlite', 'encoding' => 'utf8', 'cacheMetadata' => true, 'quoteIdentifiers' => false, ],
  • 10.
    Bake a database $bin/cake bake migration CreateMembers first_name:string last_name:string email:string created modified $ bin/cake bake migration CreateEvents title:string description:text start:datetime end:datetime created modified $ bin/cake bake migration CreateEventsMembers event_id:integer:primary member_id:integer:primary
  • 11.
    Bake a database Themigration bake task can sometimes get confused with composite primary keys, and tries to make each primary key an autoincrement field. We need to fix that! Open config/Migrations/…_CreateEventsMembers.php and remove the two ‘autoIncrement’ => true, lines. If you are using SQLite, you need to open all the files in the config/Migrations, and change all instances of ‘default’ => null to be ‘default’ => ‘’
  • 12.
    Seed that database $bin/cake bake seed Members Open config/Seeds/MembersSeed.php, and update the $data variable $data = [ [ 'first_name' => 'Walther', 'last_name' => 'Lalk', 'email' => 'waltherlalk@cakephp.org', 'created' => date('Y-m-d H:i:s'), 'modified' => date('Y-m-d H:i:s'), ] ];
  • 13.
    Seed that database $bin/cake bake seed Events Open config/Seeds/EventsSeed.php, and update the $data variable $data = [ [ 'title' => 'PHP South Africa 2016', 'description' => '', 'start' => '2016-09-28 08:00', 'end' => '2016-09-30 17:00', 'created' => date('Y-m-d H:i:s'), 'modified' => date('Y-m-d H:i:s'), ] ];
  • 14.
    Running migrations $ bin/cakemigrations migrate $ bin/cake migrations seed
  • 15.
    Bake your application $bin/cake bake all Members $ bin/cake bake all Events Visit /members in your browser, there should be 1 member. Add another one. Now visit /events in your browser, there should be 1 event. Add another one. Notice how both of them have a multi-select box for members or events? We’re going to remove that at a later stage and change how it works.
  • 16.
    Anatomy of aCakePHP application
  • 17.
  • 18.
  • 19.
    Validation Open src/Model/Table/EventsTable.php, findin the validationDefault method. Change notEmpty() to be allowEmpty() $validator ->requirePresence('description', 'create') ->notEmpty('description');
  • 20.
    Application rules public functionbuildRules(RulesChecker $rules) { $rules->add( function (Event $event) { return $event->start <= $event->end; }, 'endAfterStart', [ 'errorField' => 'end', 'message' => 'The event cannot end before it has started' ] ); return parent::buildRules($rules); }
  • 21.
    The art ofknowing who’s there
  • 22.
    Adding a password $bin/cake bake migration AddPasswordToMembers password:string[60] $ bin/cake migrations migrate Add to src/Model/Entity/MemberEntity.php protected function _setPassword($password) { if (strlen($password) > 0) { return (new CakeAuthDefaultPasswordHasher())->hash($password); } return $this->password; }
  • 23.
    Adding a password Addto src/Templates/Members/add.ctp and src/Templates/Members/edit.ctp echo $this->Form->input('password', [ 'value' => '' ]); echo $this->Form->input('password_confirm', [ 'label' => 'Confirm password', ’type' => 'password', 'value' => '' ]);
  • 24.
    Adding a password Addvalidation to the password, and password confirmation fields. $validator ->requirePresence('password', 'create') ->notEmpty('password', 'You need to provide a password.', 'create') ->minLength('password', 6, 'Your password must be 6 characters or longer'); $condition = function ($context) { return !empty($context['data']['password']); }; $validator ->requirePresence('password_confirm', $condition) ->notEmpty('password_confirm', 'Please confirm your password', $condition) ->add('password_confirm', 'mustMatch', [ 'rule' => function ($check, $context) { return $context['data']['password'] === $check; }, 'on' => $condition, 'message' => 'Password does not match' ]);
  • 25.
    Adding a password Edityour existing members and give them passwords. They will be automatically hashed.
  • 26.
    Creating the loginaction Open src/Controller/MembersController.php public function login() { if ($this->request->is('post')) { $member = $this->Auth->identify(); if ($member) { $this->Auth->setUser($member); return $this->redirect($this->Auth->redirectUrl()); } else { $this->Flash->error(__('Email address or password is incorrect'), [ 'key' => 'auth' ]); } } } public function logout() { return $this->redirect($this->Auth->logout()); }
  • 27.
    Creating the logintemplate Create src/Templates/Members/login.ctp <div class="login form large-12 columns content"> <?php echo $this->Form->create(); echo $this->Form->input('email'); echo $this->Form->input('password'); echo $this->Form->button('Login'); echo $this->Form->end(); ?> </div>
  • 28.
    Enabling Authentication Open src/Controller/AppController.php,and add to the initialize method. $this->loadComponent('Auth', [ 'loginAction' => [ 'controller' => 'Members', 'action' => 'login', ], 'authenticate' => [ 'Form' => [ 'fields' => ['username' => 'email', 'password' => 'password'], 'userModel' => 'Members' ] ] ]); Try to visit /members now
  • 29.
    Event security We areadding a organiser to our events. Only the organiser is allowed to change the event. $ bin/cake bake migration AddOrganiserToEvents organiser_id:integer $ bin/cake migrations migrate $this->belongsTo('Organiser', [ 'foreignKey' => 'organiser_id', 'className' => 'Members' ]); Open src/Model/Table/EventsTable.php, and add to the initialize method. Open src/Model/Table/MembersTable.php, and add to the initialize method. $this->hasMany('OrganisingEvents', [ 'foreignKey' => 'organiser_id', 'className' => 'Events' ]);
  • 30.
    Event security Enforce foreignkey constraints at an application level by adding the following to the buildRules method in the EventsTable class. $rules->existsIn('organiser_id', 'Organiser', 'That member does not exist in the database.');
  • 31.
    Who’s the organiser? Themember creating the event should be able to choose who the event organiser is. In src/Templates/Events/add.ctp echo $this->Form->input('organiser_id', [ 'empty' => '-- Select organiser --', 'options' => $members, 'default' => $this->request->session()->read('Auth.User.id') ]); In src/Templates/Events/edit.ctp echo $this->Form->input('organiser_id', [ 'empty' => '-- Select organiser --', 'options' => $members, ]);
  • 32.
    Who’s that? Who isorganiser 1? protected $_virtual = [ 'full_name' ]; protected function _getFullName() { return sprintf('%s %s (%s)', $this->first_name, $this->last_name, $this->email); } Add to the Member entity class In the MembersTable class, change the displayField to ‘full_name’ Better!
  • 33.
    Event security To enforcethat an event can only be modified by it’s organiser, open src/Controller/EventsController.php and add to the edit method, just after the get() call. if ($event->organiser_id !== $this->Auth->user('id')) { throw new ForbiddenException(); }
  • 34.
    Member security To preventa member from editing another member’s profile, simply add if ($id !== $this->Auth->user('id')) { throw new ForbiddenException(); } To the edit method in the MembersController.
  • 35.
    Allow registrations Allow newmembers to register by adding a beforeFilter method to the MembersController. public function beforeFilter(CakeEventEvent $event) { $this->Auth->allow('add'); return parent::beforeFilter($event); } Add make a pretty URL for registration and login. Add to config/routes.php $routes->connect('/register', ['controller' => 'Members', 'action' => 'add']); $routes->connect('/login', ['controller' => 'Members', 'action' => 'login']);
  • 36.
  • 37.
    Belongs to many Createa join table object $ bin/cake bake model EventsMembers Change 'joinTable' => 'events_members' to 'through' => 'EventsMembers', 'saveStrategy' => CakeORMAssociationBelongsToMany::SAVE_APPEND in the Members and Events table objects.
  • 38.
    Event attendance Add afield to capture the type of event attendance $ bin/cake bake migration AddTypeToEventsMembers type:string[10] $ bin/cake migrations migrate Add to EventsMember entity const TYPE_GOING = 'going'; const TYPE_INTERESTED = 'interested'; const TYPE_NOT_GOING = 'notGoing';
  • 39.
    Event attendance In EventsTable,add public function linkMember(AppModelEntityEvent $event, $memberId, $type) { $member = $this->Members->get($memberId); //Add the join data $member->_joinData = new AppModelEntityEventsMember([ 'type' => $type ]); return $this->association('Members')->link($event, [$member]); }
  • 40.
    Event attendance In EventsController,add public function linkActiveMember($eventId, $type) { $event = $this->Events->get($eventId); if ($this->Events->linkMember($event, $this->Auth->user('id'), $type)) { $this->Flash->success('Registered!'); } else { $this->Flash->error('Something went wrong.'); } return $this->redirect($this->referer()); }
  • 41.
    Event attendance In theEvent entity public function memberStatus($memberId, $type) { if (!$this->has('members')) { return false; } $member = collection($this->members) ->firstMatch(['id' => $memberId]); if (!$member) { return false; } return $member->_joinData->type === $type; }
  • 42.
    Event attendance In src/Template/Events/index.ctp,in the actions table cell <br> <?php if (!$event->memberStatus($this->request->session() ->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_GOING) ) { echo $this->Html->link(__('Going'), [ 'action' => 'linkActiveMember', $event->id, AppModelEntityEventsMember::TYPE_GOING ]); } if (!$event->memberStatus($this->request->session() ->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_INTERESTED) ) { echo $this->Html->link(__('Interested'), [ 'action' => 'linkActiveMember', $event->id, AppModelEntityEventsMember::TYPE_INTERESTED ]); } if (!$event->memberStatus($this->request->session() ->read('Auth.User.id'), AppModelEntityEventsMember::TYPE_NOT_GOING) ) { echo $this->Html->link(__('Not going'), [ 'action' => 'linkActiveMember', $event->id, AppModelEntityEventsMember::TYPE_NOT_GOING ]); } ?>
  • 43.
    Event attendance Open src/Templates/Events/add.ctpand src/Templates/Events/edit.ctp and remove echo $this->Form->input('members._ids', ['options' => $members]); Open src/Templates/Members/add.ctp and src/Templates/Members/edit.ctp and remove echo $this->Form->input('events._ids', ['options' => $events]);
  • 44.
    We don’t needcode where we’re going
  • 45.
    Installing CRUD $ composerrequire friendsofcake/crud $ bin/cake plugin load Crud use CrudControllerControllerTrait; Add the Crud trait to your AppController And, load the Crud component in the initialize method $this->loadComponent('Crud.Crud', [ 'actions' => [ 'Crud.Index', 'Crud.View', 'Crud.Edit', 'Crud.Add', 'Crud.Delete' ], 'listeners' => [ 'Crud.RelatedModels' ] ]);
  • 46.
    Remove all yourcode Or, at least all index, view, edit, add and delete actions.
  • 47.
    It still works!? Thanksto the power of CRUD, everything still works (Mostly)
  • 48.
    Adding back functionality Createa AuthListener class in the src/Listener directory <?php namespace AppListener; use CakeEventEvent; use CakeNetworkExceptionForbiddenException; use CrudListenerBaseListener; /** * Class AuthListener */ class AuthListener extends BaseListener { /** * Settings * * @var array */ protected $_defaultConfig = [ 'property' => 'id', 'message' => 'You are not allowed to access the requested resource.', 'actions' => ['edit', 'delete'] ];
  • 49.
    Adding back functionality publicfunction afterFind(Event $event) { if (!in_array($this->_request()->param('action'), $this->config('actions')) ) { return; } $entity = $event->subject()->entity; $userId = $this->_controller()->Auth->user('id'); if ($entity->get($this->config('property')) !== $userId) { throw new ForbiddenException($this->config('message')); } } }
  • 50.
    Adding back functionality MembersControllerbeforeFilter, add: $this->Crud->addListener('Auth', 'Auth', [ 'property' => 'id' ]); Add a beforeFilter for EventsController public function beforeFilter(CakeEventEvent $event) { $this->Crud->addListener('Auth', 'Auth', [ 'property' => 'organiser_id' ]); return parent::beforeFilter($event); }
  • 51.
  • 52.