Faster develoment with CakePHP 3

Post on 29-Nov-2014

1.517 views 1 download

description

 

Transcript of Faster develoment with CakePHP 3

Faster applicationdevelopment with

CakePHP 3.0

1 / 33

Agenda1. A short story2. Grandpas3. Overview of CakePHP 34. Des raisons pour dire ouaou5. Let's get Baking

2 / 33

A Short StoryCakePHP as a project was created in late 2004.

You were probably wearing diapers at the time.

3 / 33

More than a grown upCakePHP is already a grandpa in the frameworks world

4 / 33

Types of Grandpas

The dormant

All cool kids make fun of him.

5 / 33

Types of Grandpas

The bad inluence

All cool kids want to be like him. They do not end well.

6 / 33

Types of Grandpas

The magic one

Has a great deal of experience, helps people in their journeys.

7 / 33

Why CakePHP 3?Very mature. Experience from lots of development years is added into the mix.

Extremely powerful, flexible and lightweight ORM.

Beautifully simple Form generator for single and multiple entities.

Built-in Internationalization, that actually makes sense.

Middleware oriented stack, without sacrificing familiarity and readability.

Solves the 80% problem and stays out of the way.

Outstanding documentation.

Requires less code!

8 / 33

Use it separatelyInteroperability with the rest of the PHP ecosystem

Separate components that you can use in other projects

cakephp/cache

cakephp/event

cakephp/validation

cakephp/collection...

use Cake\Collection\Collection;$items = [ ['id' => 1, 'name' => 'foo', 'parent' => 'a'], ['id' => 2, 'name' => 'bar', 'parent' => 'b'], ['id' => 3, 'name' => 'baz', 'parent' => 'a'],];

$combined = (new Collection($items))->combine('id', 'name', 'parent');

// Result will look like this when converted to array[ 'a' => [1 => 'foo', 3 => 'baz'], 'b' => [2 => 'bar']];

9 / 33

Quick ORM OverviewEverything can be an expression

$query = $users->find()->select(['id'])->where(['is_active' => true]);$anotherQuery->innerJoin(['stuff' => $query]);$anotherQuery->where(['id IN' => $query]);

Queries can be composed

$premium = $users->find('active')->find('premium')->each(function($user) { echo $user->name;});

$subscribers = $users->find('active')->find('subscribedToNewsletter');

Queries have access to powerful collection methods

$recipients = $premium->append($subscribers)->extract('email');

10 / 33

Quick ORM OverviewFiltering by associations

public function findWithCitiesBiggerThanDenmark(Query $query) { $denmarkPopulation = $this->find() ->select(['population']) ->where(['name' => 'Denmark']);

return $query ->matching('Cities', function($q) use ($denmarkPopulation) { return $q->where(['Cities.population >' => $denmarkPopulation]); });}

11 / 33

Quick ORM OverviewLazy results post-processing

public function findInContinentGroups(Query $query) { $query->formatResults(function($results) { return $results->groupBy('continent'); }); return $query;}

"Africa": [ { "name": "Angola" }, { "name": "Burundi" }, { "name": "Benin" }, { "name": "Burkina Faso" }"America": [...

12 / 33

Quick ORM OverviewLazy result processing pipelines

public function findInRegionalGroups(Query $query) { $query ->formatResults(function($results) { return $results->groupBy('continent'); }) ->formatResults(function($results) { return $results->map(function($continent) { return collection($continent)->groupBy('region'); }); }); return $query;}

"North America": { "Caribbean": [ { "name": "Aruba" }, { "name": "Anguilla" }, { "name": "Netherlands Antilles" } ...

13 / 33

MOAR!Map-Reduce

Intelligent count operations

Complex pagination one-liners

Automatic saving of associations

Flexible association strategies

Result streaming

Query caching

Finder callbacks

Composite Primary Key searches

Methods for finding in Tree structures

14 / 33

MOAR!

Find more ORM examples at

https://github.com/lorenzo/cakephp3-examples

15 / 33

Let's Get Baking

16 / 33

Install CakePHPcomposer create-project --prefer-dist -s dev cakephp/app

cd app && bin/cake server

Virtual environment available: https://github.com/FriendsOfCake/vagrant-chef

17 / 33

Initial database migration$this->table('bookmarks') ->addColumn('user_id', 'integer') ->addColumn('title', 'string', ['limit' => 50]) ->addColumn('description', 'text') ->addColumn('url', 'text') ->addColumn('created', 'datetime') ->addColumn('modified', 'datetime') ->create();

$this->table('tags') ->addColumn('title', 'string', ['limit' => 100]) ->addColumn('created', 'datetime') ->addColumn('modified', 'datetime') ->create();...

$ bin/cake migrations create Initial$ bin/cake migrations migrate

Reversible migrations provided by http://phinx.org

18 / 33

Put the Cake in the Oven$ bin/cake bake all users$ bin/cake bake all bookmarks$ bin/cake bake all tags

19 / 33

That's it

The End

20 / 33

DebugKitCakePHP 3 comes with a debug toolbar pre-installed:

21 / 33

Next level RADThe code in the controllers looks repetitive, how do we fix this?

How can we have both a Web + REST interface ?

Can we make this without sacrificing readability and flexibility?

Enter the CRUD Plugin

Dynamic Event-Driven Scaffolding

Automatic REST API generation

Actions Classes

Debugging Enhancements

22 / 33

Install the CRUD plugincomposer require friendsofcake/crud:dev-cake3

class AppController extends Controller {

use \Crud\Controller\ControllerTrait;

public function initialize() { $this->loadComponent('Crud.Crud', [ 'actions' => [ 'Crud.Index', 'Crud.Add', 'Crud.Edit', 'Crud.View', 'Crud.Delete' ] ]); }}

Remove all the code from your controllers.

Piece of Cake!

23 / 33

Creating a REST API// config/routes.php

$routes->extensions(['json', 'xml']); // optional

public function initialize() { ... $this->loadComponent('RequestHandler'); $this->Crud->addListener('Crud.Api');}

$ curl -X GET localhost:8765/tags.json

Seriously, that's it

Converts all actions into a web service able to respond in JSON and XML.

Takes care of error handling and validation errors.

Automatically adds a "layout" to your responses (useful for pagination)

Wins you fame and glory beyond your wildest dreams. 24 / 33

Adding Debug info to APIpublic function initialize() { ... $this->Crud->addListener('Crud.Api'); $this->Crud->addListener('Crud.ApiQueryLog');}

25 / 33

Authentication

Hash Passwords

// src/Model/Entity/User.php

use Cake\Auth\DefaultPasswordHasher;...protected function _setPassword($password) { $hasher = new DefaultPasswordHasher(); return $hasher->hash($value);}

Enable Authentication

public function initialize() { $this->loadComponent('Auth');}

Make sure you create a user before this last step.

You will now be prompted for login on all actions. 26 / 33

The Login Action// src/Controller/UsersController.php

public function login() { if ($this->request->is('post')) { $user = $this->Auth->identify(); if ($user) { $this->Auth->setUser($user); return $this->redirect($this->Auth->redirectUrl()); } $this->Flash->error('Your username or password is incorrect.'); }}

And its template

// src/Template/Users/login.ctp<h2>Login</h2><?= $this->Form->create() ?><?= $this->Form->input('username') ?><?= $this->Form->input('password') ?><?= $this->Form->button('Login') ?><?= $this->Form->end() ?>

27 / 33

Permissions

Create the permissions handler

// src/Auth/BookmarksAuthorizeclass BookmarksAuthorize { public function authorize($user, Request $request) { $action = $request->action;

// The add and index actions are always allowed. if (in_array($action, ['index', 'add'])) { return true; }

// All other actions require an id. if (empty($request->params['pass'])) { return false; }

// Check that the bookmark belongs to the current user. $id = $request->params['pass'][0]; $bookmark = $this->registry->getController()->Bookmarks->get($id); if ($bookmark->user_id == $user['id']) { return true; } }}

28 / 33

Permissions

Attach the permissions handler

// src/Controller/BookmarksController.php

public function initialize() { $this->Auth->config('authorize.Bookmarks', [ 'className' => 'App\Auth\BookmarksAuthorize' ]);}

Users will now be able to only see Bookmarks they created themselves.

Yes, that easy.

No, I'm not kidding.

29 / 33

Event Based Crud

Limiting the records by user

// src/Controller/BookmarksController.php$this->Crud->on('beforePaginate', function() { $this->paginate['conditions'] = ['Bookmarks.user_id' => $this->Auth->user('id')];});

Remembering the Bookmark creator

// src/Controller/BookmarksController.php

$this->Crud->on('beforeSave', function($e) { $e->subject->entity->user_id = $this->Auth->user('id');});

30 / 33

Custom Finders

Bookmarks tagged with...

public function findTagged(Query $query, $options) { return $query->matching('Tags', function($q) use ($options) { return $q->where(['Tags.title' => $options['tag']]); });}

Find

$bookmarks->find('tagged', ['tag' => 'awesome']);

31 / 33

Thanks

Find examples at

https://github.com/lorenzo/cakephp3-bookmarkr

https://github.com/lorenzo/cakephp3-examples

32 / 33

Rate the talk!https://joind.in/talk/view/11946

33 / 33