phpluna/framework

一个简单的PHP MVC框架

v2.0.4 2024-06-10 18:44 UTC

This package is auto-updated.

Last update: 2024-09-10 19:21:35 UTC


README

Luna是一个以PHP开发的框架,受到Laravel、CodeIgniter和JavaScript的Express等其他框架的启发,专注于Web开发,具有以下功能:

  • 路由映射;
  • ORM数据库(Illuminate/Eloquent);
  • 中间件队列;
  • 实用性、安全性和敏捷性;
  • 简化托管;
  • 缓存存储;
  • 组件化;
  • 分页;
  • 搜索引擎优化(SEO)。

摘要

学习Luna

安装

在开始Luna项目之前,您需要安装PHP(版本7.1或更高版本)和Composer

使用以下命令以Luna启动项目

composer create-project phpluna/luna {project-name}

初始化

将文件.env.example重命名为.env并按需配置URL

mv .env.example .env

如果您愿意,可以复制该文件并保留.env.example以保留环境变量的示例。

项目的一般设置可以在同一文件.env中定义,例如,可以定义第三方API的认证密钥。

路由

应在/routes目录下的某个文件中创建路由(假定您正在遵循这里展示的模式)。您可以创建分离的文件,例如pages.php用于页面路由或api.php用于API路由。

<?php
    use App\Controllers\Pages;

    $router->get('/', [
        function($request, $response) {
            return Pages\Home::homePage($request, $response);
        }
    ]);

可用的路由方法

$router->get($uri, [$callback]);
$router->post($uri, [$callback]);
$router->put($uri, [$callback]);
$router->patch($uri, [$callback]);
$router->delete($uri, [$callback]);
$router->options($uri, [$callback]);

可以为同一个$uri$callback定义多个方法。

$router->match(['get', 'post'], $uri, [$callback]);
$router->any($uri, [$callback]);

路由参数

路由可以接收自定义参数。

$router->get('/products/{id}', [
    function($request, $response) {
        return Pages\Product::getPage($request, $response);
    }
]);

可以在执行函数中获取参数。

class Product extends Page {
    public static function getPage($request, $response) {
        $id = $req->param('id');
    }
}

如果您愿意,也可以通过显式变量获取URL参数。

$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) {
        // ...
    }
}

可选参数

可以使用?创建可选参数。

$router->get('/cart/{id?}', [
    function($request, $response) {
        // ...
    }
]);

$router->get('/product/{id}/{slug?}', [
    function($request, $response) {
        // ...
    }
]);

未在请求中提供的选择参数将被定义为NULL。

错误路由

可以在定义路由时直接处理一些常见的错误,以自定义返回页面。

use \App\Controllers\Errors;

$router->error(404, [
    function($request, $response) {
        return Errors\PageNotFound::getPage($request, $response);
    }
]);

还可以为任何错误定义一个默认路由。

$router->error('default', [
    function($request, $response) {
        return Errors\General::getPage($request, $response);
    }
]);

重定向路由

要执行路由重定向,请使用redirect()函数。

$router->get('/redirect', [
    function($request, $response) {
        return $request->getRouter()->redirect('/destination');
    }
]);

中间件

中间件提供了一种方便的机制来验证特定路由的请求。

$router->get('/', [
    'middlewares' => [ 'maintenance' ],
    function($request, $response) {
        // ...
    }
]);

中间件类必须包含一个名为handle的函数,该函数将在访问路由时执行。

namespace App\Middlewares;

class Maintenance {
    public function handle($request, $response, $next) {
        // ...

        return $next($request, $response);
    }
}

handle函数必须接受$request$response$next参数,并必须返回$next($request, $response)以继续队列。

创建中间件类后,需要用别名定义它,以便在定义路由时使用。

use Luna\Http\Middleware;

Middleware::setMap([
    'maintenance' => \App\Middlewares\Maintenance::class
]);

可以定义标准中间件,这些中间件将在创建的所有路由中执行。

Middleware::setDefault([
    'maintenance'
]);

缓存

将路由返回存储在缓存中可以减少相同路由未来请求的返回时间

$router->get('/', [
    'cache' => 10000,
    function($request, $response) {
        // ...
    }
]);

缓存时间以毫秒为单位定义

缓存配置可以在.env文件中定义

当路由缓存被设置为true时,CACHE_TIME的值被定义为缓存时间(也以毫秒为单位)

$router->get('/', [
    'cache' => true,
    function($request, $response) {
        // ...
    }
]);

控制器

路由(大多数情况下)执行控制器(Controllers)

namespace App\Controllers;

class Product {
    public static function productPage($request, $response) {
        // ...
    }
}

获取请求数据

可以通过$request变量获取请求数据,如headers、查询参数、body等

$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

可以使用以下函数获取特定参数:$request->query('id')。不指定参数将导致所有参数作为数组返回。

响应请求

每个请求都必须被响应,并且其响应应该在控制器的函数的return中完成

class Product {
    public static function productPage($request, $response) {
        return $response->send(200, "Sucesso");
    }
}

建议遵循此处列出的HTTP状态码标准(例如,示例中的200):链接

请求响应类型

请求的响应可以返回text/htmlapplication/json(更常见)或其他(较少见)的值

public static function getProduct($request, $response) {
    // ...
    return $response->send(200, ["data" => "Sucesso"], "application/json");
}

还可以使用别名来返回HTML或JSON中的请求

$res->send(200, $content, 'json'); // Ao invés de 'application/json'
$res->send(200, $content, 'html'); // Ao invés de 'text/html'

仅在DEFAULT_CONTENT_TYPE.env文件值与控制器期望的不同时,才应该在函数中指定响应类型

服务

服务(Services)帮助在数据库和控制器之间获取和处理数据

namespace App\Services;

class Product {
    public static function find($id) {
        // ...
    }
}

服务使用

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);
    }
}

辅助函数

助手(Helper)将一些小的、未被定义为服务的有用函数组合在一起

namespace App\Helpers;

class Uuid {
    public function generate() {
        // ...
    }
}

助手使用

use App\Helpers\Uuid as UuidHelper;

class User {
    public function find($id) {
        return UuidHelper::generate();
    }
}

视图

视图可以在resources/views中创建为.html文件,并在渲染中使用

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);
    }
}

resources/view/page/product.html文件

<h1>{{name}}</h1>
<p>{{description}}</p>

<!-- Resultado: -->
<!-- <h1>Produto nome</h1> -->
<!-- <p>Produto descrição</p> -->

要访问数组的不同级别,请使用->,例如

<p>{{name->first}}</p>
<p>{{phone->main->number}}</p>

将获得以下值

$data = [
    'phone' => [
        'main' => [
            'number' => '12345'
        ]
    ],
    'name' => [
        'full' => 'Fulano de Tal',
        'first' => 'Fulano',
    ],
]

未发送的变量可以使用??接收预定义的值

<p>{{name ?? Nome não definido}}</p>

视图变量遵循与组件相同的规则,因此两者都可以使用所有资源

页面规范

Page类具有一些函数,可以用于使用headerfooter和其他标准项来标准化页面,并更改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);
    }
}

使用Page类时,变量$content将包含page.htmlheader.htmlfooter.html文件(已存在于/resources/view中)的合并

还可以添加新的标准文件到头部和底部,例如可以为公共区域和管理区域创建不同的头部

$content = parent::getPage("Produto Título", $content, [
    'header' => 'header-admin',
    'footer' => 'footer-admin'
]);

要防止添加文件,将其定义为false

标准变量

最常用的变量可以在index.htmlView::define()中定义,并可以在任何视图中使用

<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消息

Flash消息可以用于以动态方式将消息返回到视图

namespace App\Controllers\Pages;

use Luna\Utils\Flash;

class Product {
    public static function productPage($request, $response) {
        // ...
        Flash::create("productNotFound", "Produto não encontrado", 'error');
    }
}

创建消息后,可以将其渲染并添加到视图中

Flash::create("productNotFound", "Produto não encontrado", 'error');
$flash = Flash::render("productNotFound");

还可以渲染尚未预先创建的消息

$flash = Flash::render(false, "Produto não encontrado", 'error');

消息存储在会话变量$_SESSION中,如果消息在其他地方未使用,则预先创建它可能是有用的。

如果需要,可以一次渲染多个消息(仅适用于预先创建的消息)

$flashs = Flash::renderAll(["productNotFound", "productOutOfStock"]);

一旦渲染,就可以像其他参数一样将其添加到视图中

$content = View::render('pages/product', ['flash' => $flash]);

请确保在将要使用的视图中添加{{flash}}或相应的参数。

Flash消息组件可以在/resources/components/flash/alert.html中更改。

如果需要,可以在同一目录中创建一个组件,并在渲染时选择它

Flash::create("productNotFound", "Produto não encontrado", 'error', 'alert-new');
Flash::render("Produto não encontrado", 'error', 'alert-new');

示例中存在的error值应用于组件的{{type}}变量,并且可以自定义任何值以进行样式化。

常见类型包括:errordangerwarninginfosuccess

组件

视图中的小结构如果是重复的(或不重复的)则可以用作组件。

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]);
    }
}

组件应创建在.html文件中,与view相同,位于resources/components目录下。

也可以创建子目录来组织,例如:resources/components/product/card,并使用Component::render('product/card', $product)进行渲染。

多渲染

在需要多次从数组渲染相同组件的情况下

$productCards = Component::multiRender('product-card', $products);
$content = View::render('pages/product', ['productCards' => $productCards]);

分页

可以使用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();
    }
}

get()函数将返回已分页的列表和其他分页数据。

可以获取特定分页数据

$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

分页模板

分页控制可以渲染显示在视图中

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]);
    }
}

用于创建分页的组件可以在resources/components/pagination中进行修改,也可以在渲染时进行修改

$paginationRender = $pagination->render($req, [
    'last' => 'last.html'
    // ...
]);

项目的href始终使用{{page}}参数来定义目标页面。

如果需要删除某个项目,请将参数设置为false

要限制每侧显示的项目数量,请使用

$paginationRender = $pagination->render($req, [], 3);

渲染结果示例,每侧显示3个项目

Exemplo de paginação

样式应单独进行设计

数据库

Luna项目的数据库访问通过在Laravel中使用的对象关系映射(ORM)实现,称为Illuminate/Eloquent,它允许使用简单的快速函数来编写复杂的SQL查询。

数据库访问凭据的配置应在.env文件中进行,并通过以下方式建立连接:

use Luna\Db\Database;
Database::boot();

使用bootstrap.php文件时,连接将默认配置。例如:require __DIR__ . '/bootstrap.php';

可以在此处访问完整的Eloquent文档[点击访问]

迁移

迁移可以用来以程序化和版本化的方式对数据库进行修改。

要创建迁移,请使用Luna CLI

php luna make:migration {{name}}

name应包含迁移文件的名称,例如

php luna make:migration create_users_table

也可以通过特定的表创建迁移

php luna make:migration --table=users add_role_id_column_to_users_table

要执行数据库迁移,请使用以下命令

php luna migrate

这将检查哪些迁移尚未执行,并执行它们。

要执行特定的迁移,请使用以下命令

php luna migrate {{name}}

如果需要撤销最后一次执行的迁移,请使用以下命令

php luna migrate:rollback

每次执行migrate:rollback都会返回一个批次,执行最后一个批次将撤销。

要清理数据库并执行迁移,请使用以下命令

php luna migrate:fresh

出于安全考虑,需要通过--confirm-c来确认执行migrate:fresh

模型

模型遵循Illuminate/Eloquent ORM的标准

namespace App\Models;

use Luna\Db\Model;

class Product extends Model {
    // ...
}

模型的使用

namespace App\Services;

use App\Models\Product;

class Product {
    public function find() {
        return Product::find(1);
    }

    public function list() {
        return Product::all();
    }
}

SEO

可以为视图显示创建搜索引擎优化(SEO)

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]);
    }
}

函数$seo->setKeywords()可以接收数组或字符串中的关键字,例如:$seo->setKeywords("chave-1, chave-2")

如果在视图的渲染中未单独定义标题,则在parent::getPage中使用$seo->render(true)以确保<title>标签由SEO渲染。

Twitter和Meta OpenGraph

Twitter和Meta OG的配置可以单独进行

$seo = new Seo();
$seo->setTitle("Produto nome");
$seo->twitter()->setTitle("Produto nome (Twitter)");
$seo->meta()->setTitle("Produto nome (Meta)");

可以单独配置每个网络的全部tags

$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);

如果Twitter和Meta OG的数据相同,只需以下方式之一告知

$seo = new Seo();
$seo->twitter();
$seo->meta();
$seo->setTitle("Produto nome")
// ...
$seo = new Seo(['twitter', 'meta']);
$seo->setTitle("Produto nome")
// ...

如果在使用$seo->twitter()$seo->meta()之后使用$seo->setTitle()和其他设置,标题、描述和图像的定义将被共享。要禁用此功能,请在每个使用时使用$seo->twitter(false)或$seo->meta(false)。

如果类 Seo 未指定 TwitterMeta OG 则会使用 .env 文件中的 DEFAULT_SEO 中的值。

机器人

可以在渲染时添加 Robots 的配置

$seo = new Seo();

$seo->setRobots($index, $follow);

变量 $index$follow 必须是 Boolean 类型。

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);

默认情况下,索引使用跟随的链接,因此如果使用 $seo->setRobots() 函数而不传递任何参数,它将是可选的。

环境

可以使用 .env 文件来定义项目配置值,这些值可以从文件中获取

use Luna\Utils\Environment;

Environment::get($key); // Obter item específico
Environment::get(); // Obter todos os items

也可以存储不在 .env 文件中的动态值

Env::set($key, $value);

为项目做出贡献

感谢您考虑为 Luna 做出贡献!贡献指南仍在开发中,不久您将了解如何进行贡献。

在此期间,您可以自行在仓库中进行贡献。

许可证

Luna 是一个遵循 MIT 许可协议 的开源软件。