Núcleo
Controladores
Hierarquia
Controller (core/web)
├── SuperController (src) site público: templateFolder "front", acesso aberto
│ └── IndexController, CuentasController, DocsController…
├── AdminSuperController (src) painel: templateFolder "admin", exige login
│ └── Admin/Sistema/UsuariosController…
├── AjaxController (core/web) respostas JSON
│ ├── FrontAjaxController (src)
│ └── AdminAjaxController (src)
├── JavascriptController (core/web) respostas JavaScript
│ ├── FrontJavascriptController (src)
│ └── AdminJavascriptController (src)
└── ApiController (src) ponto de entrada da API REST
Sempre estenda a classe de src/ correspondente, não as de core/: é nelas que se carregam o usuário da sessão, o menu e os dados comuns de cada área.
Um controlador completo
<?php
Koshkil::Uses('com.SuperController');
Koshkil::UsesModel('productos');
class ProductosController extends SuperController {
public $selected_menu = 'productos'; // marca o item ativo do menu
protected function init() {
$retVal = parent::init(); // carrega $this->usuario, reCAPTCHA…
if ($retVal instanceof Response) {
return $retVal;
}
WebElements::addScript(Koshkil::getThemePath('js/productos.js'));
}
// GET /productos
public function index() {
$pagina = max(1, intval($this->Request->pagina));
$productos = TMProductos::order('prd_nombre')->pageSize(20)->page($pagina)->get();
$this->set([
'productos' => $productos,
'total' => $productos->totalRecords,
'webpageTitle' => 'Produtos',
]);
}
// GET /productos/ver/15
public function ver($id = null) {
$producto = TMProductos::find(intval($id));
if (!$producto) {
$this->Response->setCode(404);
return;
}
$this->set(['producto' => $producto]);
}
}
Propriedades
| Propriedade | Uso |
|---|---|
$Request |
Dados de GET e POST (veja abaixo). |
$Response |
Código, corpo, tipo MIME. |
$Session |
Sessão do usuário. |
$view |
View Smarty. Preenchida com $this->set([...]). |
$usuario |
Usuário logado (TMUsuarios) ou null. |
$templateFile |
Layout que envolve o template (main.tpl). |
$templateFolder |
Pasta de templates dentro do tema (front, admin). |
$openAccess |
true: não exige login. |
$roles, $rules, $strictRules |
Permissões necessárias (veja Segurança). |
$lifeCycle |
Hooks executados em ordem: ["dispatch","init","run"]. |
$selected_menu |
Chave do item de menu ativo (usada pelos widgets de cabeçalho). |
Passar dados para a view
$this->set([
'producto' => $producto,
'webpageTitle' => $producto->prd_nombre, // usado pelo main.tpl no <title>
'template' => 'productos/ficha', // outro template no lugar de productos/ver
]);
Variáveis com significado especial:
| Variável | Efeito |
|---|---|
template |
Template a incluir (sem .tpl, relativo à pasta do tema). |
class |
Classe CSS do <main> (se o layout a usar). |
data_controller |
Atributo data-controller do <main>; os módulos JavaScript do núcleo o usam para iniciar. |
Request
$this->Request->email; // $_GET ou $_POST (o POST tem prioridade)
$this->Request->get('email');
$this->Request['email']; // também funciona como array
$this->Request->getData(); // tudo
$this->Request->isPost();
$this->Request->isAjax();
$this->Request->isEmpty('email');
$this->Request->hasFiles();
$this->Request->file('foto'); // ['name','type','tmp_name','error','size']
$this->Request->isUploadedFile('foto');
$this->Request->uploadError('foto');
Os dados do Request não são escapados
O construtor de consultas escapa os valores de where() e having(), mas não o SQL que você escreve à mão. Valide os dados (intval(), listas permitidas) e leia Segurança nas consultas.
Response
$this->Response->setCode(404); // 200, 404, 4xx, 500…
$this->Response->setMessage('Detalhe'); // exibido pelos templates de erro
$this->Response->setMimeType('application/json');
$this->Response->setBody(json_encode($dados)); // com corpo, é enviado como está
$this->Response->asFile('relatorio.csv'); // download (com setBody)
$this->Response->redirect('/cuentas'); // redireciona e encerra
Mensagens para o usuário
Os layouts exibem flash_message e error_message (nos temas incluídos, com toastr). Se forem guardadas na sessão, sobrevivem a um redirecionamento:
$this->Session->write('flash_message', 'Dados salvos.');
return $this->Response->redirect('/cuentas');
Para preencher de novo um formulário após um erro, Koshkil::old('campo') retorna o valor enviado no POST.
Controladores AJAX
Estendem FrontAjaxController (ou AdminAjaxController) e retornam JSON com jsonize(). Como src/Controllers/Ajax/ é uma pasta, suas URLs começam com /ajax/.
<?php
// src/Controllers/Ajax/ProductosController.php -> POST /ajax/productos/buscar
Koshkil::Uses('com.FrontAjaxController');
Koshkil::UsesModel('productos');
class ProductosController extends FrontAjaxController {
public function buscar() {
if (!$this->recaptchaOk()) {
return $this->jsonize(['status' => 'error', 'message' => $this->recaptchaError()]);
}
$texto = (string)$this->Request->q; // o where() escapa o valor
$items = TMProductos::where('prd_nombre', 'like', "%{$texto}%")->take(10)->getAsArray();
return $this->jsonize(['status' => 'success', 'items' => $items]);
}
}
Componentes
Um componente é lógica compartilhada entre controladores. Fica em src/Controllers/Components/<Nome>Component.php, estende Component e recebe o controlador em initialize().
<?php
Koshkil::uses('sys.web.Controller.Component');
class CarritoComponent extends Component {
public function total() {
return array_sum($this->controller->Session->read('carrito') ?: []);
}
}
public function create() { // create() é público em Controller
$this->loadComponent('Carrito'); // src/Controllers/Components/CarritoComponent.php
}
public function index() {
$this->set(['total' => $this->Carrito->total()]);
}
Respostas JavaScript
Uma URL que termina em .js e não é um arquivo real executa a ação javascript do controlador: /js/recaptcha.js chama Js/RecaptchaController::javascript(). Estenda FrontJavascriptController para que a resposta saia com o tipo text/javascript.