Best practices for Joomla extensions developers - Joomla Day 2013

Preview:

DESCRIPTION

Do you write extensions for Joomla? Do it the *right* way. You will save time and make friends amongst fellow developers (because they won't hate you when they have to read your code). In this session we will share standards and suggestions about the best practices to adopt when you code your extensions. Based on a true story. Our own.

Citation preview

Joomla Extensions Development Best Practices

Francesco Abeni GiBiLogicextensions.gibilogic.com

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

¡Hola, mundo!

Shameless self-promotion

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

FrancescoAbeni

sPrintAddCSSPizzaBox

About this speech

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

The quality of code in the Joomlasphere

Today roadmap:

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● Tools● Files and folders● Reuse software● MVC● Other tips● Conclusions

Feedback please!

No dev course

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Our target

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Good = not bad

Excellent = above the average

Good is enough for today

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

IDE basic features

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● multiple files edit● syntax highlighting● index for methods and variables● autocompletion● autoformatting● compiler● versioning / unit testing / phpdoc / ...

Some IDEs

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Versioning

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

http://git-scm.com/book

Standard

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Everything in its right place

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Backend ● administrator○ components

■ com_componentname● componentname.xml● componentname.php● controllers● models● views

Everything in its right place

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Backend ● administrator○ components

■ com_componentname● ...● config.xml● install.php● sql● tables● helpers

● media○ com_componentname

■ css■ js■ img

● components○ com_componentname

■ componentname.php■ controllers■ models■ views

Everything in its right place

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Frontend

● images○ com_componentname

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

CSS / JS

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Use existing libraries

JavaScript● MooTools (since Joomla 1.5)● JQuery (since Joomla 2.5)

CSS + JavaScript● Bootstrap (since Joomla 3.x)

P.S. got conflicts? Use JQueryEasy plugin.

CSS out of the door

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Don't:<div>...</div><br style="clear: both"><div style="height: 200px">...</div>

Do:<link rel=”stylesheet” href=”/media/componentname/styles.css”>...<div class=”clearfix”>...</div><div class="fixedheight">...</div>

.clearfix { … }

.fixedheight { height: 200px }

JS out of the door

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<button id="submit" type=submit" value="ClickMe!" onclick="validateForm()" />

Do:<script src=”/media/componentname/js/script.js”><button id="submit" type=submit" value="ClickMe!"/>

document.addEvent('load',function(){ $('submit').addEvent('click',function(){

validateForm(); });

});

Don't:

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Joomla framework

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● JApplication● JDatabase● JUser● JSession● JDocument● JHTML● JForm● JConfig● JUri

● JFile● JFolder● JLog● JFilterInput ● JError and JException● JDate● JUtilities● JVersion● JLayout

PHP functions and classes

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● pcre● trim● usort● array_map● array_unique● json_encode● json_decode● microtime(true)● glob● curl

● DateTime● Standard PHP Library● Exception● SimpleXML● TCPDF● PHPMailer

PHP version

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● 16. Dec 2010: PHP 5.2 end of life● 11. Jul 2013: PHP 5.3 end of life● 01. Mar 2012: PHP 5.4 released ● 20 Jun 2013: PHP 5.5 released

● PHP 5.4 is 40% faster than PHP 5.2

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Real objects

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

book writer library

Bad design sample

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● views/search● views/editbook● views/book● views/books● views/booksauthor● views/topten

Don't:

The controller

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● Filters input● Decides what to do● Checks access permissions● Executes task(s)● Optionally, calls the view

The model

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● Retrieves object data● Validates object data● Gets object data● Saves object data● Hates to be mistaken as an helper

The view

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● Ask the model for data● Display object(s)● Uses layouts!

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Header comment

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php

/*** @version mybooks.php 2013-08-10 15:23:00Z zanardi* @package GiBi MyBooks* @author GiBiLogic* @authorUrl http://www.gibilogic.com* @authorEmail info@gibilogic.com* @copyright Copyright (C) 2013 GiBiLogic. All rights reserved.* @license GNU/GPL v2 or later* @description Backend entry point*/

Entry point

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php...defined('_JEXEC') or die();

jimport('joomla.application.component.controller');

$view = JFactory::getApplication()->input->get('view', 'book');$task = JFactory::getApplication()->input->get('task', 'index');JFactory::getApplication()->input->set('task', "$view.$task");

$controller = JController::getInstance('MyBooks');$controller->execute($task);$controller->redirect();

Controller - part 1

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php...defined('_JEXEC') or die('The way is shut!');

jimport('joomla.application.component.controlleradmin');

/*** MyBooksControllerBook class.** @see JControllerAdmin*/class MyBooksControllerBook extends JControllerAdmin{ /** * Controller's view. * * @var JView */ private $view; ...

Controller - part 2

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... /** * Class constructor. * * @param type $config */ public function __construct($config = array()) { parent::__construct($config);

$this->model = $this->getModel();

$this->view = $this->getView(JFactory::getApplication()->input->get('view', 'book'), 'html'); $this->view->setModel($this->model, true); $this->view->setModel($this->getModel('Author', 'MyBooksModel'), false); $this->view->setModel($this->getModel('Editor', 'MyBooksModel'), false); } ...

Controller - part 3

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... public function index() { $this->view->setLayout('index') $this->view->display(); }

public function create() { $this->view->setLayout('create'); $this->view->display(); }

public function save() { $data = JFactory::getApplication()->input->get('jform', null); if (!$data || !$this->model->validate($data)) { $msg = 'Invalid data!'; $type = 'error'; $this->setRedirect('index.php?option=com_mybooks&view=book&task=create', $msg, $type); return false; }

Model - part 1

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php...defined('_JEXEC') or die('The way is shut!');

jimport('joomla.application.component.model');jimport('joomla.html.pagination');

class MybooksModelBook extends JModel{ private $table = '#__mybooks_book';

public function __construct($config = array()) { parent::__construct($config);

$app = JFactory::getApplication(); $limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->getCfg('list_limit'), 'int'); $limitstart = $app->input->get('limitstart', 0, '', 'int'); $this->setState('limit', $limit); $this->setState('limitstart', $limitstart); $this->setState('author_id', $app->getUserStateFromRequest('com_mybooks.filters.author_id', 'author_id', 0, 'int')); }

Model - part 2

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... public function getList() { return $this->_getList( $this->buildQuery(), $this->getState('limitstart'), $this->getState('limit') ); }

public function getLast() { $query = $this->_db->getQuery(true); $query->select('*')->from($this->table)->orderby('created_at DESC'); $this->_db->setQuery($query,0,1); $results = $this->_db->loadObjectList('id'); return $results ? $results : array(); }

public function getLastByAuthor($author_id) { $query = $this->_db->getQuery(true); $query->select('*')->from($this->table)->where(“author_id = '$author_id'”)->orderby('created_at DESC'); $this->_db->setQuery($query,0,1); return $this->_db->loadObject(); }

Model - part 3

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... public function create($data) { if (!$data) { return 0; }

$data['created_at'] = date('Y-m-d H:i:s');

$query = $this->_db->getQuery(true);

$query->insert($this->table)->columns(array_keys($data))->values(sprintf("'%s'", implode("','", array_values($data)))); $this->_db->setQuery($query);

return false === $this->_db->execute() ? 0 : $this->_db->insertid(); } ...

Model - part 4

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... public function delete($ids) { $query = $this->_db->getQuery(true); $query->delete()->from($this->table)->where('id IN '.implode(',', $ids)); return false !== $this->_db->execute(); }

public function getPagination(){ return new JPagination( $this->_getListCount($this->buildQuery()), $this->getState('limitstart'), $this->getState('limit') ); }

private function buildQuery(){ $where = $this->buildWhere(); $query = $this->_db->getQuery(true); return $query->select('*')->from($this->table)->where($where)->orderby('created_at DESC'); } ...

View - part 1

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php...defined('_JEXEC') or die('The way is shut!');

jimport('joomla.application.component.view');

class MyBooksViewBook extends JView{ public function display($tpl = null) { $this->pagination = $this->getModel()->getPagination(); $this->filter_author_id = $this->getModel()->getState('author_id');

$this->books = $this->getModel()->findAll(); $this->authors = $this->getModel('Authors')->getList(); $this->editors = $this->getModel('Editors')->getList();

$this->addToolbar($tpl);

parent::display($tpl); }

View - part 2

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php ... protected function addToolbar($tpl){ $methodName = 'addToolBar' . ucfirst(!$tpl ? 'default' : $tpl); $this->{$methodName}(); }

private function addToolBarDefault(){ JToolBarHelper::title(JText::_('COM_MYBOOKS') . ': ' . JText::_('COM_MYBOOKS_BOOK_LIST')); JToolBarHelper::addNew('create'); JToolBarHelper::preferences('com_mybooks'); JToolBarHelper::divider(); JToolBarHelper::deleteList('COM_MYBOOKS_BOOK_LIST_DELETE_CONFIRM', 'delete'); }

private function addToolBarCreate(){ JToolBarHelper::title(JText::_('COM_MYBOOKS') . ': ' . JText::_('COM_MYBOOKS_BOOK_NEW')); JToolBarHelper::apply('save'); JToolBarHelper::divider(); JToolBarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mybooks'); }}

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Helpers

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Common (usually static) functions not related to a specific object

● Get date / time / external info● Format date and numbers● Build title and/or other HTML snippets● Log/error management● Handle CURL connections

Table classes

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Interface to and from the database

Active Record pattern

● Define table name and unique id● load, store, delete, and so on

Table classes - sample code

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

<?php

class TableBook extends JTable { public function __construct(&$db) { parent::__construct(‘#__books’, ‘id’, $db); } }

Layouts

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Page types related to a single view (object)"tmpl" subfolder (template override)

● List● Single item (readonly)● Single item (edit form)● Blog● ...

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

System messages

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

$app = JFactory::getApplication();$app->enqueueMessage( $msg, $type )

JError / JException / JLog

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Error handling vs. logging

● JError is deprecated● JException is deprecated● Use PHP Exception class(es)

● JLog is a way to track what's happening

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Visibility

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Variables and methods

● "var ..." is deprecated since PHP 5.1.2● public : available from other classes● private : available only from the class● protected : available from the class and

from inherited or parent classes

Constants

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● constants instead of variables● drop DS● drop DIRECTORY_SEPARATOR● use Joomla constants:

http://docs.joomla.org/Constants● warning: JPATH_SITE vs JPATH_BASE vs

JPATH_ROOT vs JPATH_ADMINISTRATOR

Versioning

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

● version format: major.minor.release (es. v3.1.5)● variant: v3.1.5 Free, v3.1.5 Pro

And the road goes on and on

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Thanks :)

f.abeni@gibilogic.com@f_abeni / @gibilogic

http://www.slideshare.net/FrancescoAbeni/best-practices-for-joomla-extensions-developers-25353320

Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - info@gibilogic.com

Feedback please!