Com'era strutturato Joomla 1.5?
Procediamo oggi con un simpatico esperimento, andiamo ad analizzare quello che veniva fatto diversi anni fa in uno dei più popolari CMS dell'epoca, ovvero Joomla 1.5. Questo è il codice della funzione save() che si occupava di salvare il contenuto di un argomento nel file controller.php del componente com_content.
<?php
function save()
{
// Check for request forgeries
JRequest::checkToken() or jexit( 'Invalid Token' );
// Initialize variables
$db = & JFactory::getDBO();
$user = & JFactory::getUser();
$task = JRequest::getVar('task', null, 'default', 'cmd');
// Make sure you are logged in and have the necessary access rights
if ($user->get('gid') < 19) {
JError::raiseError( 403, JText::_('ALERTNOTAUTH') );
return;
}
// Create a user access object for the user
$access = new stdClass();
$access->canEdit = $user->authorize('com_content', 'edit', 'content', 'all');
$access->canEditOwn = $user->authorize('com_content', 'edit', 'content', 'own');
$access->canPublish = $user->authorize('com_content', 'publish', 'content', 'all');
if (!($access->canEdit || $access->canEditOwn)) {
JError::raiseError( 403, JText::_("ALERTNOTAUTH") );
}
//get data from the request
$model = $this->getModel('article');
//get data from request
$post = JRequest::get('post');
$post['text'] = JRequest::getVar('text', '', 'post', 'string', JREQUEST_ALLOWRAW);
//preform access checks
$isNew = ((int) $post['id'] < 1);
if ($model->store($post)) {
$msg = JText::_( 'Article Saved' );
if($isNew) {
$post['id'] = (int) $model->get('id');
}
} else {
$msg = JText::_( 'Error Saving Article' );
JError::raiseError( 500, $model->getError() );
}
// manage frontpage items
//TODO : Move this into a frontpage model
require_once (JPATH_ADMINISTRATOR.DS.'components'.DS.'com_frontpage'.DS.'tables'.DS.'frontpage.php');
$fp = new TableFrontPage($db);
if (JRequest::getVar('frontpage', false, '', 'boolean'))
{
// toggles go to first place
if (!$fp->load($post['id']))
{
// new entry
$query = 'INSERT INTO #__content_frontpage' .
' VALUES ( '.(int) $post['id'].', 1 )';
$db->setQuery($query);
if (!$db->query()) {
JError::raiseError( 500, $db->stderr());
}
$fp->ordering = 1;
}
}
else
{
// no frontpage mask
if (!$fp->delete($post['id'])) {
$msg .= $fp->stderr();
}
$fp->ordering = 0;
}
$fp->reorder();
$model->checkin();
// gets section name of item
$query = 'SELECT s.title' .
' FROM #__sections AS s' .
' WHERE s.scope = "content"' .
' AND s.id = ' . (int) $post['sectionid'];
$db->setQuery($query);
// gets category name of item
$section = $db->loadResult();
$query = 'SELECT c.title' .
' FROM #__categories AS c' .
' WHERE c.id = ' . (int) $post['catid'];
$db->setQuery($query);
$category = $db->loadResult();
if ($isNew)
{
// messaging for new items
require_once (JPATH_ADMINISTRATOR.DS.'components'.DS.'com_messages'.DS.'tables'.DS.'message.php');
// load language for messaging
$lang =& JFactory::getLanguage();
$lang->load('com_messages');
$query = 'SELECT id' .
' FROM #__users' .
' WHERE sendEmail = 1';
$db->setQuery($query);
$users = $db->loadResultArray();
foreach ($users as $user_id)
{
$msg = new TableMessage($db);
$msg->send($user->get('id'), $user_id, JText::_('New Item'), JText::sprintf('ON_NEW_CONTENT', $user->get('username'), $post['title'], $section, $category));
}
} else {
// If the article isn't new, then we need to clean the cache so that our changes appear realtime :)
$cache = &JFactory::getCache('com_content');
$cache->clean();
}
if ($access->canPublish)
{
// Publishers, admins, etc just get the stock msg
$msg = JText::_('Item successfully saved.');
}
else
{
$msg = $isNew ? JText::_('THANK_SUB') : JText::_('Item successfully saved.');
}
$referer = JRequest::getString('ret', base64_encode(JURI::base()), 'get');
$referer = base64_decode($referer);
if (!JURI::isInternal($referer)) {
$referer = '';
}
$this->setRedirect($referer, $msg);
}Come vedete, all'epoca i controller erano un pò i "camerieri in sala", quelli che in teoria dovrebbero fare da raccordo tra cucina e clienti ma che, nella pratica di tutti i giorni, si occupano anche di altro (come guarnire le porzioni dei dolci prima di servirle, condire le insalate e predisporre i conti per la cassa).
In Joomla abbiamo un esempio di cosa oggi non andrebbe mai fatto, viste le evoluzioni che ha offerto nel frattempo Php. Se all'epoca era normale trovare controller con oltre 350 righe di codice e metodi, come quello appena visto, lunghi più di 130 righe, oggi le best practice guidano gli sviluppatori verso controller minimali e deleghe ad altri tipi di componenti, come Repository per l'accesso ai dati e Service Layer per la logica di business.
Il controller/metodo oggi assumerebbe una forma più simile a questa:
<?php
final class ArticleController
{
public function __construct(
private ArticleService $service
) {}
public function save(Request $request): Response
{
$this->service->save(
new SaveArticleDTO(
id: $request->input('id'),
title: $request->input('title'),
text: $request->input('text'),
sectionId: $request->input('sectionid'),
categoryId: $request->input('catid'),
isFrontPage: $request->boolean('frontpage'),
userId: $request->user()->id
)
);
return new RedirectResponse(
$request->input('ret', '/'),
'Article saved successfully'
);
}
}dove il codice si limita a 'orchestrare' al meglio le chiamate ad altri componenti, che svolgeranno il lavoro vero e proprio. Avremo un Service Layer, in questo caso dedicato agli articoli:
<?php
final class ArticleService
{
public function __construct(
private ArticleRepository $articles,
private FrontPageRepository $frontPage,
private UserRepository $users,
private EventDispatcher $events
) {}
public function save(SaveArticleDTO $dto): void
{
$isNew = $dto->id === null;
$article = $this->articles->save($dto);
$this->handleFrontPage($article->id, $dto->isFrontPage);
if ($isNew) {
$this->notifyNewArticle($article, $dto->userId);
} else {
$this->articles->clearCache();
}
}
}anche la logica del frontpage (vale a dire il codice che pubblica gli articoli nella prima pagina, all'epoca di Joomla 1.5 era una feature essenziale) verrebbe gestita da un altro service con un metodo come questo:
<?php
$this->frontPageService->handleFrontPage(...);e nel service:
<?php
public function handleFrontPage(int $articleId, bool $isFrontPage): void
{
if ($isFrontPage) {
$this->frontPage->add($articleId);
} else {
$this->frontPage->remove($articleId);
}
}Una parte delle responsabilità che un tempo appartenevano ai Model viene oggi affidata ai Repository e i dati vengono trasmessi attraverso il DTO. Vediamo meglio questi aspetti.
"La vera efficienza si trova nella serenità." (Henry David Thoreau)
Questo sito rispetta gli utenti: