best practices for joomla extensions developers - joomla day 2013
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.TRANSCRIPT
Joomla Extensions Development Best Practices
Francesco Abeni GiBiLogicextensions.gibilogic.com
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
¡Hola, mundo!
Shameless self-promotion
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
FrancescoAbeni
sPrintAddCSSPizzaBox
About this speech
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
The quality of code in the Joomlasphere
Today roadmap:
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
● Tools● Files and folders● Reuse software● MVC● Other tips● Conclusions
Feedback please!
No dev course
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Our target
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Good = not bad
Excellent = above the average
Good is enough for today
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
IDE basic features
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
● 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 - [email protected]
Versioning
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
http://git-scm.com/book
Standard
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Everything in its right place
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Backend ● administrator○ components
■ com_componentname● componentname.xml● componentname.php● controllers● models● views
Everything in its right place
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
Frontend
● images○ com_componentname
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
CSS / JS
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
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 - [email protected]
<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 - [email protected]
Joomla framework
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
● 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 - [email protected]
● 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 - [email protected]
● 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 - [email protected]
Real objects
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
book writer library
Bad design sample
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
● views/search● views/editbook● views/book● views/books● views/booksauthor● views/topten
Don't:
The controller
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
● 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 - [email protected]
● 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 - [email protected]
● Ask the model for data● Display object(s)● Uses layouts!
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Header comment
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
<?php
/*** @version mybooks.php 2013-08-10 15:23:00Z zanardi* @package GiBi MyBooks* @author GiBiLogic* @authorUrl http://www.gibilogic.com* @authorEmail [email protected]* @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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
<?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 - [email protected]
Helpers
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
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 - [email protected]
<?php
class TableBook extends JTable { public function __construct(&$db) { parent::__construct(‘#__books’, ‘id’, $db); } }
Layouts
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
System messages
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
$app = JFactory::getApplication();$app->enqueueMessage( $msg, $type )
JError / JException / JLog
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
Visibility
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
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 - [email protected]
● 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 - [email protected]
● 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 - [email protected]
Thanks :)
[email protected]@f_abeni / @gibilogic
http://www.slideshare.net/FrancescoAbeni/best-practices-for-joomla-extensions-developers-25353320
Francesco Abeni for GiBiLogichttp://extensions.gibilogic.com - [email protected]
Feedback please!