phpluna / framework
Um simples framework MVC em PHP
Requires
- php: >=7.1
- illuminate/database: ^8.83
This package is auto-updated.
Last update: 2025-03-07 21:22:26 UTC
README
Luna é um framework desenvolvido em PHP com inspirações em outros frameworks como Laravel, CodeIgniter e o Express do JavaScript voltado para desenvolvimento Web com recursos como:
- Mapeamento de rotas;
- Banco de dados em ORM (Illuminate/Eloquent);
- Fila de middlewares;
- Praticidade, segurança e agilidade;
- Hospedagem simplificada;
- Armazenamento em cache;
- Componentização;
- Paginação;
- Search Engine Optimization (SEO).
Sumário
- Aprendendo Luna (Documentação)
- Contribuindo com o projeto
- Licença
Aprendendo Luna
Instalação
Antes de iniciar seu projeto Luna, é necessário realizar a instalação do PHP (versão 7.1 ou superior) e Composer.
Utilize o comando para iniciar um projeto com Luna:
composer create-project phpluna/luna {project-name}
Inicializando
Renomeie o arquivo .env.example
para .env
e configure a URL conforme necessário:
mv .env.example .env
Caso prefira, duplique o arquivo e mantenha o .env.example
para que versione os exemplos de variáveis de ambiente.
As configurações gerais do projeto podem ser definidas no mesmo arquivo
.env
, como por exemplo a chave de autenticação de alguma API de terceiros.
Rotas
A criação de rota deve ser realizada em algum arquivo do diretório /routes
(levando em consideração que você está seguindo o padrão apresentado aqui). É possível criar arquivos de separação, como pages.php
para rotas de páginas ou api.php
para rotas da API:
<?php use App\Controllers\Pages; $router->get('/', [ function($request, $response) { return Pages\Home::homePage($request, $response); } ]);
Métodos de rota disponíveis
$router->get($uri, [$callback]); $router->post($uri, [$callback]); $router->put($uri, [$callback]); $router->patch($uri, [$callback]); $router->delete($uri, [$callback]); $router->options($uri, [$callback]);
Também é possível definir múltiplos métodos para um mesmo $uri
e $callback
:
$router->match(['get', 'post'], $uri, [$callback]); $router->any($uri, [$callback]);
Parâmetros de rota
As rotas podem receber parâmetros personalizados:
$router->get('/products/{id}', [ function($request, $response) { return Pages\Product::getPage($request, $response); } ]);
Os parâmetros podem ser obtidos na função executada:
class Product extends Page { public static function getPage($request, $response) { $id = $req->param('id'); } }
Caso prefira, também é possível obter os parâmetros da URL através de uma variável explicita:
$router->get('/products/{id}', [ function($id, $request, $response) { return Pages\Product::getPage($id, $request, $response); } ]); class Product extends Page { public static function getPage($id, $request, $response) { // ... } }
Parâmetros opcionais
Os parâmetros opcionais podem ser criados utilizando ?
:
$router->get('/cart/{id?}', [ function($request, $response) { // ... } ]); $router->get('/product/{id}/{slug?}', [ function($request, $response) { // ... } ]);
Parâmetros opcionais não informados na requisição serão definidos como NULL.
Rotas de erros
Alguns erros comuns podem ser tratados diretamente na definição da rota para personalizar a página de retorno:
use \App\Controllers\Errors; $router->error(404, [ function($request, $response) { return Errors\PageNotFound::getPage($request, $response); } ]);
Também é possível definir uma rota padrão para qualquer erro:
$router->error('default', [ function($request, $response) { return Errors\General::getPage($request, $response); } ]);
Rotas de direcionamento
Para realizar um redirecionamento em alguma rota, utilize a função redirect()
:
$router->get('/redirect', [ function($request, $response) { return $request->getRouter()->redirect('/destination'); } ]);
Middlewares
Os middlewares fornecem um mecanismo conveniente para validar requisições em rotas específicas:
$router->get('/', [ 'middlewares' => [ 'maintenance' ], function($request, $response) { // ... } ]);
A classe do Middleware deve conter a função handle
que será executada ao acessar a rota:
namespace App\Middlewares; class Maintenance { public function handle($request, $response, $next) { // ... return $next($request, $response); } }
A função handle
deve receber os parâmetros $request
, $response
e $next
e deve retornar $next($request, $response)
para prosseguir com a fila.
Após criar a classe do Middleware, é necessário defini-lo com um apelido para que seja utilizado na definição da rota:
use Luna\Http\Middleware; Middleware::setMap([ 'maintenance' => \App\Middlewares\Maintenance::class ]);
É possível definir middlewares padrões
que serão executados em todas as rotas criadas:
Middleware::setDefault([ 'maintenance' ]);
Cross-Origin Resource Sharing (CORS)
O CORS pode ser configurado na inicialização da aplicação:
use Luna\Http\Cors; Cors::setOrigins(["*"]); // Origens permitidas Cors::setMethods(["*"]); // Métodos permitidos Cors::setHeaders(["*"]); // Headers permitidos Cors::setCredentials(true); // Aprovar credenciais Cors::setMaxAge(0); // Tempo limite de cache
O Luna retornará automaticamente as requisições OPTIONS com os métodos disponíveis para uma rota.
Cache
O armazenamento do retorno de rotas em cache reduz o tempo de retorno para futuras requisições da mesma rota:
$router->get('/', [ 'cache' => 10000, function($request, $response) { // ... } ]);
O tempo de cache é definido em milisegundos
As configurações de cache podem ser definidas no arquivo .env
:
Configuração | Descrição |
---|---|
CACHE_TIME | Valor padrão de cache |
CACHE_DIR | Diretório de armazenamento do cache |
ALLOW_NO_CACHE_HEADER | Permitir o header Cache-Control: no-cache |
O valor de CACHE_TIME é definido como tempo de cache (também em milisegundos) quando o cache da rota for definido como true:
$router->get('/', [ 'cache' => true, function($request, $response) { // ... } ]);
Controllers
As rotas executam (em sua maioria) Controllers:
namespace App\Controllers; class Product { public static function productPage($request, $response) { // ... } }
Obtendo dados da requisição
Os dados da requisição como heaaders, parâmetros query, body e outros podem ser obtidos através da variável $request
:
$request->header(); // Obter parâmetros do header $request->query(); // Obter parâmetros da query $request->body(); // Obter parâmetros do corpo $request->param(); // Obter parâmetros da URL $request->getUri(); // Obter URI $request->getHttpMethod(); // Obter método HTTP
É possível obter parâmetros específicos com as funções:
$request->query('id')
. Não especificar um parâmetro fará com que todos sejam retornados em array.
Respondendo a requisição
Toda requisição deve ser respondida e sua resposta deve ser realizada no return
da função do Controller:
class Product { public static function productPage($request, $response) { return $response->send(200, "Sucesso"); } }
Recomenda-se seguir o padrão de Status HTTP (200 no exemplo) listados aqui.
Tipos de resposta da requisição
As respostas da requisição podem retornar valores em text/html
, application/json
(mais comuns) ou outros (menos comuns):
public static function getProduct($request, $response) { // ... return $response->send(200, ["data" => "Sucesso"], "application/json"); }
É possível também utilizar alias para o retorno da requisição em HTML ou JSON:
$res->send(200, $content, 'json'); // Ao invés de 'application/json' $res->send(200, $content, 'html'); // Ao invés de 'text/html'
O tipo de resposta só deve ser informado na função caso o valor de DEFAULT_CONTENT_TYPE
do arquivo .env
seja diferente do desejado para o Controller.
Services
Os Services auxiliam na obtenção e tratamento de dados entre o Banco de Dados e o Controller:
namespace App\Services; class Product { public static function find($id) { // ... } }
Uso do service:
use App\Services\Product as ProductService; class Product { public static function getProduct($request, $response) { $id = $request->param("id"); $product = ProductService::find($id); return $response->send(200, $product); } }
Helpers
Um Helper agrupa pequenas funções úteis e que não são definidas como Services:
namespace App\Helpers; class Uuid { public function generate() { // ... } }
Uso do Helper:
use App\Helpers\Uuid as UuidHelper; class User { public function find($id) { return UuidHelper::generate(); } }
Views
As views podem ser criadas em resources/views
em .html
e utilizadas na renderização:
namespace App\Controllers\Pages; use Luna\Utils\View; class Product { public static function productPage($request, $response) { $content = View::render('pages/product', [ 'name' => "Produto nome", 'description' => "Produto descrição" ]); return $response->send(200, $content); } }
Arquivo resources/view/page/product.html
:
<h1>{{name}}</h1> <p>{{description}}</p> <!-- Resultado: --> <!-- <h1>Produto nome</h1> --> <!-- <p>Produto descrição</p> -->
Para acessar diferentes níveis do array
, utilize ->
, por exemplo:
<p>{{name->first}}</p> <p>{{phone->main->number}}</p>
Obteria os valores, de:
$data = [ 'phone' => [ 'main' => [ 'number' => '12345' ] ], 'name' => [ 'full' => 'Fulano de Tal', 'first' => 'Fulano', ], ]
Variáveis que não são enviadas podem receber um valor pré-definido com ??
:
<p>{{name ?? Nome não definido}}</p>
As variáveis da View seguem as mesmas regras dos Components, sendo assim, ambos conseguem utilizar todos os recursos.
Padronização de página
A classe Page
possui funções que permitem padronizar as páginas com header
, footer
e outros itens padrões, alterando o valor de content
:
namespace App\Controllers\Pages; use Luna\Utils\View; class Product extends Page { public static function productPage($request, $response) { $content = View::render('pages/product', [ 'name' => "Produto nome", 'description' => "Produto descrição" ]); $content = parent::getPage("Produto Título", $content); return $response->send(200, $content); } }
Com uso da classe Page
a variável $content
irá conter a junção dos arquivos page.html
, header.html
e footer.html
(já existentes em /resources/view
).
É possíve também adicionar novos arquivos padrões para cabeçalho e rodapé, podendo por exemplo criar diferentes cabeçalhos para a área pública e área administrativa:
$content = parent::getPage("Produto Título", $content, [ 'header' => 'header-admin', 'footer' => 'footer-admin' ]);
Para que os arquivos não sejam adicionadas, defina-o como false
.
Variáveis padrões
As variáveis mais comuns podem ser definidas no arquivo index.html
em View::define()
e podem ser utilizadas em qualquer View:
<img src="{{PUBLIC}}/assets/img/php-logo.png" /> <a href="{{URL}}"><button>Início</button></a> <a href="{{URL}}/products"><button>Produtos</button></a>
Flash Messages
A Flash Message pode ser utilizada para retornar mensagens para a view de forma dinâmica:
namespace App\Controllers\Pages; use Luna\Utils\Flash; class Product { public static function productPage($request, $response) { // ... Flash::create("productNotFound", "Produto não encontrado", 'error'); } }
Após criar uma mensagem é possível renderiza-la para adicionar em uma view:
Flash::create("productNotFound", "Produto não encontrado", 'error'); $flash = Flash::render("productNotFound");
É possível também renderizar uma mensagem que não tenha sido criada previamente:
$flash = Flash::render(false, "Produto não encontrado", 'error');
O armazenamento das mensagens é realizado na variavel de sessão
$_SESSION
, não cria-la previamente pode ser útil quando a mensagem não for utilizada em outros locais.
Caso deseje, renderize diversas mensagens de uma vez (apenas para mensagens criadas previamente):
$flashs = Flash::renderAll(["productNotFound", "productOutOfStock"]);
Uma vez renderizada, adicione-a na view assim como outros parâmetros:
$content = View::render('pages/product', ['flash' => $flash]);
Certifique-se de adicionar o parâmetro {{flash}} ou correspondente na view que será utilizada.
O componente das mensagens flashs pode ser alterado em /resources/components/flash/alert.html
.
Se necessário, é possível criar um componente no mesmo diretório e seleciona-lo na renderização:
Flash::create("productNotFound", "Produto não encontrado", 'error', 'alert-new'); Flash::render("Produto não encontrado", 'error', 'alert-new');
O valor de error
presente nos exemplos é aplicado na variável {{type}}
do componente e pode ser personalizado com qualquer valor para estilização.
Os tipos comuns são:
error
,danger
,warning
,info
,success
.
Components
Pequenas estruturas de uma view que sejam repetidas (ou não) podem ser utilizadas como um componente:
namespace App\Controllers\Pages; use Luna\Utils\Component; class Product { public static function productsPage($request, $response) { // ... $productCard = Component::render('product-card', $product); $content = View::render('pages/product', ['productCard' => $productCard]); } }
O componente deve ser criado em .html
assim como a view
no diretório resources/components
.
É possível também criar subpastas para organizar, por exemplo:
resources/components/product/card
e renderizar comComponent::render('product/card', $product)
.
Renderização múltipla
Em situações onde o mesmo componente deve ser renderizado diversas vezes a partir de um array
:
$productCards = Component::multiRender('product-card', $products); $content = View::render('pages/product', ['productCards' => $productCards]);
Pagination
A paginação de arrays
para listagem pode ser realizada com uso da classe Pagination
:
namespace App\Controllers\Pages; use Luna\Utils\Pagination; class Product { public static function productsPage($request, $response) { // ... $pagination = new Pagination($products, $page, $limit); $products = $pagination->get(); } }
A função get()
retornará a lista já paginada e outros dados sobre a paginação.
É possível obter dados especificos da paginação:
$pagination->getCount(); // Obter quantidade de itens da página atual $pagination->getList(); // Obter lista de itens da página atual $pagination->getPages(); // Obter quantidade de páginas $pagination->getPage(); // Obter página atual $pagination->getLimit(); // Obter quantidade de itens por página $pagination->getTotal(); // Obter quantidade total de itens
Template de paginação
O controle da paginação pode ser renderizado para ser exibido na View:
namespace App\Controllers\Pages; use Luna\Utils\Pagination; class Product { public static function productsPage($request, $response) { // ... $pagination = new Pagination($products, $page, $limit); $paginationRender = $pagination->render($req); $content = View::render('pages/products', ['pagination' => $paginationRender]); } }
Os componentes utilizados na criação da paginação podem ser modificados em resources/components/pagination
e também podem ser alterados na renderização:
$paginationRender = $pagination->render($req, [ 'last' => 'last.html' // ... ]);
O
href
dos itens sempre utilizará o parâmetro {{page}} para definir a página destino.
Caso seja necessário remover algum item, defina o parâmetro como false
.
Para limitar a quantidade de itens exibidos para cada lado do item atual, utilize:
$paginationRender = $pagination->render($req, [], 3);
Exemplo de resultado da renderização com 3 itens para cada lado:
Estilização deve ser realizada separadamente
Database
O acesso ao Banco de Dados de projetos Luna são realizados através do Object-Relational Mapping (ORM) utilizado no Laravel chamado Illuminate/Eloquent e o mesmo permite o uso de funções simples e rápidas para escrever querys SQLs complexas.
A configuração das credênciais de acesso ao banco de dados deve ser realizada no arquivo .env
e a conexão é estabelecida com:
use Luna\Db\Database; Database::boot();
Ao utilizar o arquivo
bootstrap.php
a conexão é configurada por padrão. Ex:require __DIR__ . '/bootstrap.php';
.
Acesse a documentação completo do Eloquent aqui.
Migrations
A Migration pode ser utilizada para criar modificações no banco de dados de forma programática e versionada.
Para criar uma migration, utilize o Luna CLI:
php luna make:migration {{name}}
O valor name
deve conter o nome do arquivo de migração, por exemplo:
php luna make:migration create_users_table
É possível também criar a migration com uma tabela específica:
php luna make:migration --table=users add_role_id_column_to_users_table
Para realizar a migração do banco de dados utilize o comando:
php luna migrate
Isto irá verificar quais migrações ainda não foram executadas e executa-las.
Para executar uma migração especifica, utilize o comando:
php luna migrate {{name}}
Caso precise voltar atrás com a última migração realizada, utilize o comando:
php luna migrate:rollback
Cada execução de
migrate:rollback
retornará um lote, ao executar o último lote será desfeito.
Para limpar o banco de dados e executar as migrações, utilize o comando:
php luna migrate:fresh
Por segurança, é necessário confirmar a execução do
migrate:fresh
com--confirm
ou-c
.
Models
O Model segue o padrão do ORM Illuminate/Eloquent:
namespace App\Models; use Luna\Db\Model; class Product extends Model { // ... }
Uso do model:
namespace App\Services; use App\Models\Product; class Product { public function find() { return Product::find(1); } public function list() { return Product::all(); } }
SEO
O Search Engine Optimization (SEO) pode ser criado para exibição na View:
namespace App\Controllers; use Luna\Utils\Seo; class Product { public static function getProduct($request, $response) { // ... $seo = new Seo(); $seo->setTitle("Produto nome"); $seo->setDescription("Produto descrição"); $seo->setKeywords(["produto-chave-1", "produto-chave-2"]); $seo->setImage("produto.png"); $seo->setAuthor("Autor nome"); $seoRender = $seo->render(); $content = View::render('pages/product', ['seo' => $seoRender]); } }
A função
$seo->setKeywords()
pode receber as chaves em array ou em string, como por exemplo:$seo->setKeywords("chave-1, chave-2")
.
Caso não utilize um título definido separadamente na renderização da view em
parent::getPage
utilize$seo->render(true)
para que a tag<title>
seja renderizada pelo SEO.
Twitter e Meta OpenGraph
A configuração para Twitter e Meta OG podem ser realizadas separadamente:
$seo = new Seo(); $seo->setTitle("Produto nome"); $seo->twitter()->setTitle("Produto nome (Twitter)"); $seo->meta()->setTitle("Produto nome (Meta)");
É possível configurar todas as tags
separadamente para cada rede:
$seo->twitter()->setTitle($title); $seo->twitter()->setDescription($description); $seo->twitter()->setCard($card); $seo->twitter()->setSite($site); $seo->twitter()->setImage($image); $seo->twitter()->setUrl($url); $seo->meta()->setTitle($title); $seo->meta()->setDescription($description); $seo->meta()->setUrl($url); $seo->meta()->setImage($image); $seo->meta()->setType($type);
Caso os dados para Twitter e Meta OG sejam iguais, basta informar de uma das seguintes formas:
$seo = new Seo(); $seo->twitter(); $seo->meta(); $seo->setTitle("Produto nome") // ...
$seo = new Seo(['twitter', 'meta']); $seo->setTitle("Produto nome") // ...
Caso utilize
$seo->twitter()
ou$seo->meta()
após o uso de$seo->setTitle()
e outros, as definições de título, descrição e imagem serão compartilhadas, para desativar essa função utilize $seo->twitter(false) ou $seo->meta(false) no primeiro uso de cada.
Caso a classe Seo
seja inicializada sem definir o Twitter ou Meta OG o valor definido no arquivo .env
em DEFAULT_SEO
será utilizado.
Robots
A configuração de Robots podem ser adicionadas na renderização:
$seo = new Seo(); $seo->setRobots($index, $follow);
As variáveis $index
e $follow
devem ser Boolean
.
Exemplos de de definição do Robots:
// Página indexada e com links seguidos: $seo->setRobots(); // Página não indexada: $seo->setRobots(false); // Página com links não seguidos: $seo->setRobots(true, false); // Página não indexada e links não seguidos: $seo->setRobots(false, false);
Por padrão a indexação utiliza links seguidos, então se for utilizar a função
$seo->setRobots()
sem passar nenhum parâmetro, ela se torna dispensável.
Environment
O arquivo .env
pode ser utilizado para definir valores de configurações do projeto e podem ser obtidas em arquivos:
use Luna\Utils\Environment; Environment::get($key); // Obter item específico Environment::get(); // Obter todos os items
É possível também armazenar valores dinamicamente que não estejam presentes no arquivo .env
:
Env::set($key, $value);
Contribuindo com o projeto
Obrigado por considerar contribuir com o Luna! O guia de contribuição ainda encontra-se em desenvolvimento e em breve poderá entender como fazê-lo.
Enquanto isso, você pode realizar contribuições por conta própria no repositório.
Licença
O Luna é um software de código aberto sob a MIT License.