Elemental是从头开始开发的PHP框架,可动态,用户友好的编码体验。它结合了依赖注入之类的功能,并遵循MVC架构来简化Web开发并改善代码组织。它以对简单性和灵活性的热情设计,它邀请开发人员进入一个领域,在那里他们可以进行无与伦比的控制,并对可以使用的工具有深刻的了解。
为了展示Elemental的功能,使用Elemental开发了一个名为Inkwell的完整平台。 Inkwell是一个独特的空间,致力于讲故事的纯粹本质。与Elemental没有外部依赖关系的目标一致,仅使用普通HTML,CSS,JS和PHP制作Inkwell。
可以随意深入研究实时平台和相应的代码库。探索Inkwell的功能,以了解如何为自己的项目利用元素。
请参阅创建元素背后的灵感。
元素的设计目的是没有附加弦。外部库或框架没有依赖性。目的是给开发人员一种真正的控制感,这是一个独立探索和理解为框架提供动力的神奇物品的开放之门。
总体目标?让开发人员充分拥抱并利用强大的抽象(例如DI容器,Orms,Middlewares等)的优雅。但是这是踢脚 - 元素不仅仅是指向道路。它正在递给您揭开奥秘的钥匙,使您能够探索代码中如何布置这些抽象。
实际上,不仅鼓励您走这条路,还鼓励您冒险脱颖而出。潜入代码库,剖析抽象并了解其内部工作。随意进行调整和实验,因为元素不仅是一个框架,而且是塑造和塑造工具可供使用的开放邀请。因为编码不应该是迷宫;这应该是一段旅程。让我们一起旅行。
与其他框架不同,Elemental不依赖作曲家或外部库。这就像克隆存储库并在系统上安装了良好的OL'PHP一样简单。
打开终端并执行以下命令:
git clone https://github.com/aneesmuzzafer/elemental.git不用担心包装管理人员或依赖项 - 元素是从头开始构建的,可以使您摆脱此类担忧。
对于那些喜欢作曲家路线的人,创建一个新的Elemental应用程序只是一个命令:
composer create-project fragment/elemental sample-app这将使用composer.json文件生成一个项目。
一旦您的项目准备就绪,使用我们的命令行引擎蜡烛使用ignite命令来启动Elemental Local Development Server:
cd sample-app
php candle ignite瞧!您的应用程序现在可以在http://127.0.0.1:8000上访问。
我们已经照顾了基本设置,因此您可以专注于魔术。
让附魔开始!
元素的最重要特征是它用于管理类依赖和执行依赖注入的依赖性注入容器。
依赖注入是软件开发中的一种设计模式,该模式涉及组件如何保持其依赖性。在传统系统中,班级负责创建自己的依赖性。有了DI,创建和提供依赖项的责任就在班级之外移动。它们不是创建其依赖性的类,而是从外部来源“注入”类。
DI有助于实现松散耦合和更可维护的代码。它通过允许每个班级专注于其特定功能而不必担心如何创建或获得其依赖性来促进关注点的分离。
依赖注射是称为控制反转(IOC)的更广泛概念的特定实现。 IOC代表一个设计范式,其中程序的控制流倒置或移交给外部实体,容器或框架。
在Elemental中,当您使用依赖项注入(DI)时,如果类不依赖任何其他类,或者仅依赖于具体类(不是抽象的接口),则无需明确告诉DI容器如何创建该类的实例。 DI容器将自动弄清楚。
容器将尝试创建类的实例,如果该类对其他类具有依赖性,则容器将递归地尝试解决这些依赖关系。这个过程一直持续到所有必要的类都成功解决。因此,您不必手动指定如何创建每个类 - DI容器为您负责。
<?php
class MailService {
public function __construct ( private MailerAgent $ mailer ) {
}
}
// Inside some other class
class UserController {
public function sendMail ( MailService $ mailService )
{
$ mailService -> sendMail ();
}
}在这里,通过在方法参数中键入MailService ,Elemental能够解析该类并创建此类实例并将其传递给sendMail ,以便您可以使用它而不必担心MailService类所需的依赖项。如您所见, MailService本身取决于其他一些类MailerAgent ,但是,Elemental负责解决幕后MailerAgent类,在创建它的实例并为您使用该实例的同时,将其传递给了MailService 。
“那么,仅通过将类名称键入elemental起作用,这种注入依赖性将在哪里?”所有类constructor功能,所有controller methods和命令创建类的handle方法。
在幕后,Elemental通过查看已注册的任何绑定,将类或接口解决到具体实例中。换句话说,为了明确说明如何解决特定类或接口的实例的框架,您需要使用Application实例上的bind方法,传递我们希望注册的类或接口,以及返回类的实例:
app ()-> bind (MailService::class, function () {
// Run some logic, for example, decide on the mail agent to be passed to its constructor depending on some factors.
return new MailService ( app ()-> make (MailAgent::class));
});请注意,通常只有在需要运行一些其他逻辑以解决类别或需要将接口绑定到具体实现时,才需要绑定类。否则,Elemental将在不明确要求您绑定的情况下解决该类。
singleton方法将类或接口与容器结合,以确保仅解决一次。在初始分辨率之后,任何随后的调用对相同绑定的容器的调用都将返回相同的对象实例。
app ()-> singleton (DatabaseConnection::class, function () {
return new DatabaseConnection ( ' localhost ' , ' username ' , ' password ' );
});
// Later in the code
$ databaseConnection1 = app ()-> make (DatabaseConnection::class);
$ databaseConnection2 = app ()-> make (DatabaseConnection::class);
// $databaseConnection1 and $databaseConnection2 will reference the same instance虽然在应用程序中的任何位置注册绑定的任何位置是完全可以的,但是在应用程序进行引导时通常需要绑定它,以便应用程序的其他组件可以开始使用它。 Elemental提供了一个特殊的位置,可以注册应用程序的所有绑定并执行您应用程序所需的任何其他自举逻辑。这是AppBootstrapAppServiceProvider 。应用服务提供商包含register和boot方法。
在register方法中,您应该将事物绑定到依赖项注入容器中。但是,您不应该尝试解决任何绑定。路由或在register方法中运行任何其他功能。否则,您可能会意外地使用尚未加载的容器中的服务。
在所有其他服务提供商已注册后,调用此方法,授予对框架注册的所有服务的访问。您希望执行的任何初始化逻辑都应在此处放置。
<?php
namespace App Bootstrap ;
use App Services Auth ;
class AppServiceProvider
{
public function register (): void
{
app ()-> singleton (Auth::class, function () {
return new Auth ();
});
}
public function boot (): void
{
// Additional initialization logic can be placed here
}
}您可以使用make方法从DI容器中解析类实例。应用程序实例上的make方法接受您要解决的类或接口的名称:
use App Services MailService ;
$ mailService = app ()-> make (MailService::class);您也可以直接在Application程序类上使用静态方法instance获得应用程序实例。
use Core Main Application ;
use App Services MailService ;
$ mailService = Application:: instance ()-> make (MailService::class);当试图从代码组件中解析类时, make方法特别有用,在这种代码组件中不切实际地注入依赖项是不切实际的。在这种情况下,您可以明确要求应用程序的依赖项注入容器为您解决实例。
路由是在approutes.php文件中定义的,允许开发人员轻松注册各种路由以处理不同的HTTP请求。
路由通过在路线外墙上的相关方法(例如Route::get()中调用路线,并涉及将URI模式指定为第一个参数。第二个参数可以是定义负责处理请求的控制器和方法的封闭或数组。
例如:
<?php
use App Controllers AuthController ;
use Core Facade Route ;
Route:: get ( " /settings " , function () {
// handling logic goes here
});
Route:: post ( " /register " , [AuthController::class, " register " ]);每当匹配请求URI时,执行相应的闭合或控制器方法,并生成响应并将其发送回浏览器。
您可以使用以下方法注册对任何HTTP动词响应的路由:
Route::get($uri, $callback);Route::post($uri, $callback);Route::put($uri, $callback);Route::patch($uri, $callback);Route::delete($uri, $callback);Route::options($uri, $callback); 有时,您需要在路线内捕获URI的段。例如,您可能需要从URL捕获用户的ID。您可以通过定义路由参数来做到这一点:
Route:: get ( ' /user/{id} ' , function ( string $ id ) {
return ' User ' . $ id ;
});
Route:: get ( " /story/{id} " , function ( $ id ) { /*...*/ });您可以根据路由的要求定义尽可能多的路由参数:
Route:: post ( " story/edit/{id} " , [StoryController::class, " edit " ]);
Route:: get ( " story/{story_id}/comment/{comment_id} " , [StoryController::class, " comment " ]);这些也将传递到控制器方法中。
元素无缝处理您的控制器方法的必要依赖项。这允许您使用类型固定在回调签名中指定路由所需的任何依赖项。 Elemental负责自动解决并将声明的依赖项注入回调。
例如,如果您在回调中键入Hint CoreRequestRequest ,则Elemental确保当前的HTTP请求自动注入路由回调:
<?php
use Core Request Request ;
Route:: get ( ' /users ' , function ( Request $ request ) {
// ...
});您可以按任何顺序将键入依赖项和路由参数放置。
当您将模型ID作为参数传递给路由或控制器操作时,典型方法涉及查询数据库以基于该ID获取相应的模型。 Elemental通过路由模型绑定简化了此过程,为将模型实例直接注入路由的方便方式。
例如,您可以选择注入与给定ID相对应的整个用户模型实例,而不是仅将用户的ID注入您的路由中。
在路由或控制器操作的上下文中,使用类型矿体的变量名称定义模型,这些名称与路线中的特定段相匹配。例如:
use App Models User ;
Route:: get ( ' /users/{user} ' , function ( User $ user ) {
return $ user -> email ;
});有时,您可能希望使用id以外的列解决模型。为此,您可以在路由参数定义中指定列:
use App Models User ;
Route:: get ( ' /users/{user:email} ' , function ( User $ user ) {
return $ user ;
});在这种情况下,Elemental将无缝注入具有匹配请求URI相应值的电子邮件的模型实例。
当然,路由模型结合也可以与控制器方法一起使用。
如果在数据库中找不到匹配的模型实例,则该应用将抛出ModelNotFoundException 。您可以处理此类例外,并控制该应用程序在ExceptionsHandler类中抛出的任何此类行为以及其他例外。稍后再详细介绍。
使用Route::fallback方法,您可以定义一个路由,该路由在没有其他路由匹配传入请求时将执行。
Route:: fallback ( function () {
// ...
});route:list烛台命令将提供应用程序中定义的所有路由的列表:
php candle route:list与其合并路由文件中关闭中的所有请求处理逻辑,不如考虑通过“控制器”类构建此行为。控制器允许您组织相关的请求处理逻辑成一个凝聚力类。例如, UserController类可以管理与用户相关的各种传入请求,例如显示,创建,更新和删除用户。这些控制器类通常存储在app/Controllers目录中。
要生成新的控制器,您可以运行build:controller蜡烛命令。
php candle build:controller UserController这将在app/Controllers目录中生成一个名为“ usercontroller.php”的新文件。
控制器可能具有许多公共方法,这些方法将响应传入的HTTP请求:
<?php
use App Services Auth ;
namespace App Controllers ;
class AuthController
{
public function showRegister ()
{
return view ( " Register " )-> withLayout ( " layouts.DashboardLayout " );
}
public function logout ()
{
Auth:: logout ();
redirect ( " / " );
}
}创建控制器类及其方法后,您可以定义通往控制器方法的路由,如下所示:
use App Controllers UserController ;
Route:: get ( " /register " , [AuthController::class, " showRegister " ]);当收到的请求与指定的路由URI匹配时,将调用AppControllersUserController类中的showRegister方法,该方法将接收相应的路由参数。
元素服务容器负责解决所有控制器的实例。因此,您可以在控制器的构造函数中使用类型构图来指定其可能需要的任何依赖项。所陈述的依赖项将自动解决并注入控制器实例
<?php
namespace App Controllers ;
use Core Database Database ;
class UserController
{
/**
* Create a new controller instance.
*/
public function __construct (
public Database $ db ,
) {}
}除了通过构造函数注入依赖关系外,您还可以使用控制器方法中的依赖项使用类型模具。用于方法注入的常见用例是将CoreRequestRequest或任何服务实例注入您的控制器方法:
创建和管理控制器有效处理请求。
<?php
namespace App Controllers ;
use Core Request Request ;
use App Services Auth ;
class StoryController
{
public function create ( Request $ request )
{
$ data = $ request -> data ();
$ user = Auth:: user ();
$ story = Story:: create ([...]);
return redirect ( " /story/ $ story -> id " );
}
}f您的控制器方法可以从路由参数中预期输入,您可以灵活地按任何顺序列出您的参数。例如,考虑以下路由定义:
Route:: post ( " story/update/{id} " , [StoryController::class, " update " ]);您仍然可以键入CoreRequestRequest并通过定义控制器方法访问您的id参数:
<?php
namespace App Controllers ;
use Core Request Request ;
class StoryController
{
public function update ( string $ id , Request $ request )
{
// Update $story
return redirect ( " /story/ $ story -> id " );
}
}Elemental中的CoreRequestRequest类提供了一种面向对象的方法,用于与您的应用程序管理的当前HTTP请求。它促进了提交的输入,cookie和文件以及请求以及提交的文件的检索。
为了通过依赖项注入获取当前的HTTP请求实例,您可以在路由闭合或控制器方法中使用CoreRequestRequest类的类型知识。服务容器将自动注入传入的请求实例。
<?php
namespace App Controllers ;
use App Models Category ;
use Core Request Request ;
class CategoryController
{
public function store ( Request $ request )
{
$ name = $ request -> data ()[ " name " ];
$ category = Category:: where ([ " name " => $ name ]);
if ( $ category ) {
return view ( " Category " , [ " categories " => Category:: all (), " msg " => " Category already exists! " ])-> withLayout ( " layouts.DashboardLayout " );
}
Category:: create ( $ request -> data ());
redirect ( " /category " );
}
}服务容器也将自动将传入的请求注入路线关闭。
如果您的控制器方法可以从路由参数中预期输入,则可以灵活地按任何顺序列出您的参数。例如,考虑以下路由定义:
Route:: post ( " story/update/{id} " , [StoryController::class, " update " ]);您仍然可以键入CoreRequestRequest并通过定义控制器方法访问您的id参数:
<?php
namespace App Controllers ;
use Core Request Request ;
class StoryController
{
public function update ( string $ id , Request $ request )
{
// Update $story
return redirect ( " /story/ $ story -> id " );
}
}您可以使用data()方法将所有传入请求的输入数据作为array获取。无论输入请求是来自HTML表单还是XHR请求:
$ data = $ request -> data ();您可以访问Request实例中的所有用户输入,而不必担心使用哪种HTTP动词。无论使用HTTP动词, data方法都可以用于检索用户输入:
$ name = $ request -> data ()[ " name " ];CoreRequestRequest实例提供了各种检查传入HTTP请求的方法。让我们讨论以下一些最重要的方法。
您可以使用headers方法从CoreRequestRequest实例中检索请求标题。
$ headers = $ request -> headers ();您可以通过在CoreRequestRequest实例上调用method来检索请求方法。
$ method = $ request -> method ();您可以使用uri方法从CoreRequestRequest实例中检索请求URI。
$ uri = $ request -> uri ();您可以使用cookies方法从CoreRequestRequest实例中检索请求cookie。
$ cookies = $ request -> cookies ();您可以使用rawContent方法从CoreRequestRequest实例中检索原始内容。
$ content = $ request -> rawContent ();处理请求的原始内容时要小心。
您可以使用files方法从CoreRequestRequest实例中检索文件。
$ files = $ request -> files ();ip方法可用于检索向您的应用程序提出请求的客户端的IP地址:
$ ipAddress = $ request -> ip ();port方法可用于检索向您的应用程序提出请求的客户端地址:
$ port = $ request -> port ();您可以使用contentType方法从CoreRequestRequest实例中检索内容类型。
$ contentType = $ request -> contentType ();您可以使用queryString方法检索请求的查询字符串。
$ query = $ request -> queryString ();您可以使用text方法检索请求的文本内容,提供内容类型设置为text/plain
$ text = $ request -> text ();您可以使用js方法检索请求的JS内容,前提是内容类型设置为application/javascript
$ js = $ request -> js ();您可以使用html方法检索请求的HTML内容,前提是内容类型设置为text/html
$ js = $ request -> html ();您可以使用json方法检索请求的JSON内容,前提是将内容类型设置为application/json $request->data()返回传递给请求的所有JSON数据。然而,
$ jsonData = $ request -> json (); $request->data()包含所有JSON数据以及通过请求中查询参数传递的输入。但是, $request->json()可用于仅检索JSON内容。
您可以使用xml方法检索请求的XML内容,前提是将内容类型设置为application/json
$ xmlData = $ request -> xml ();预计每个路线和控制器都会产生向用户浏览器交付的响应。 Elemental提供了各种生成响应的方法。最简单的响应形式涉及直接从路由或控制器返回字符串。该框架将无缝将此字符串无缝转换为完整的HTTP响应。
Route:: get ( ' / ' , function () {
return ' Hello World ' ;
});除了从路由和控制器返回字符串外,您还可以返回数组或对象。该框架将自动将其转换为JSON响应:
Route:: get ( ' / ' , function () {
return [ 1 , 2 , 3 ];
});通常,您不会仅仅从路线操作中返回直接的字符串或数组。相反,您通常会返回CoreResponseResponse或视图的完整实例。
返回完整的Response实例使您可以自定义响应的HTTP状态代码和标题。您可以通过将响应实例键入控制器或路由闭合来注入响应实例。
use Core Response Response ;
Route:: get ( ' /home ' , function ( Response $ response ) {
$ response -> setHeader ( " content-type " , " text/plain " )
-> setStatusCode ( 200 )
-> setContent ( " Hello World " );
return $ response ;
});您当然可以从控制器返回view 。但是,如果您需要控制响应的状态和标题,但还需要返回view作为响应的内容,则可以如下执行以下操作:
use Core Response Response ;
class UserController {
public function register ( Response $ response ){
$ response -> setHeader ( " x-is_register " , " true " );
return view ( " Register " );
}
}这将自动在将发送到浏览器的视图响应上设置标头。
请记住,大多数响应方法都是可以链接的,从而使响应实例流畅地构建。
您可以通过在响应实例上使用setContent方法设置响应的内容。
$ response -> setContent ( " ... " );但是,如果要附加响应内容,则可以通过在响应实例上使用appendContent方法来做到这一点。
$ response -> appendContent ( " ... " );您可以使用setHeader方法在响应实例上设置标头
$ response -> setHeader ( " content-type " , " text/plain " );但是,如果您想同时设置多个标头,则可以使用setHeaders方法并传递一系列标头。
$ response -> setHeaders ([ " content-type " => " text/html " , ...]);您可以通过在响应实例上使用setHeader方法直接设置响应的状态代码。
$ response -> setStatusCode ( 301 );默认情况下,将为常见状态代码设置状态文本。
您可以生成一个重定向响应,其中包含将用户重定向到另一个URL所需的正确标头,通过调用静态方法redirect到CoreResponseResponse类。
use Core Response Response ;
Route:: get ( ' /dashboard ' , function () {
return Response:: redirect ( ' home/dashboard ' );
});但是,为简单起见,辅助方法redirect()也可以在全球上可用,以实现相同的功能。
use Core Response Response ;
Route:: post ( ' /story/create ' , function () {
if (! $ someCondition )
return redirect ( ' /story ' , 204 );
});您还可以通过调用CoreResponseResponse类上的静态方法JSON来生成JSON响应。传递给该方法的数据将转换为适当的JSON。您还可以选择将状态代码和标题数组作为第二和第三参数传递给该函数。
use Core Response Response ;
Route:: post ( ' /post ' , function () {
$ post = ( . . . );
return Response:: JSON ( $ post , 201 , [ " header " => " value " ]);
});中间件提供了一种方便的机制,可以检查和过滤传入的HTTP请求到您的应用程序。例如,您可以开发中间件来验证应用程序用户的身份验证状态。如果未对用户进行身份验证,则中间件将将其重定向到登录屏幕。相反,如果对用户进行身份验证,则中间件将允许该请求深入应用程序。
您可以灵活地创建其他中间件,以执行超出身份验证的各种任务。作为说明,记录中间件可以将所有传入的请求记录到您的应用程序中。这些中间件组件包含在app/middlewares目录中。
要创建新的中间件,请使用build:middleware烛台命令:
php candle build:middleware IsAuthenticated执行此命令将在app/middlewares目录中生成一个名为“ Isauthenticated”的新鲜中间件类。在此类中,创建了一个名为handle的方法,您可以在其中阐明中间件的逻辑。
在这里,我们仅在对用户进行身份验证的情况下才允许访问该路由,否则,我们将将用户重定向到login URI:
<?php
namespace App Middlewares ;
use App Services Auth ;
use Closure ;
use Core Request Request ;
class IsAuthenticated
{
public function handle ( Request $ request , Closure $ next )
{
if (!( /* authentication logic */ )) {
return redirect ( " /login " );
}
return $ next ( $ request );
}
}要将请求深入到该应用程序中,您应该使用$ $request $next回调。
将中间件视为HTTP请求在达到应用程序之前遍历的“图层”序列。每一层都可以仔细检查请求并有可能拒绝请求。
当然,中间件可以在将请求深入到应用程序之前或之后执行任务。例如,该中间件将在请求由应用程序处理后执行其任务:
<?php
namespace App Middlewares ;
use Closure ;
use Core Request Request ;
class AfterMiddleware
{
public function handle ( Request $ request , Closure $ next )
{
$ response = $ next ( $ request );
// Perform action
return $ response ;
}
}如果您想将中间件分配给特定路由,则可以在定义路线时调用middleware方法:
Route:: get ( ' /profile ' , function () {
// ...
})-> middleware (IsAuthenticated::class);您可以通过将一系列中间件名称传递给middleware方法来分配多个中间件:
Route:: get ( ' / ' , function () {
// ...
})-> middleware ([First::class, Second::class]);在定义组时,您可以通过将中间件名称的数组传递到属性middlewares的中间人名称:
Route:: group ([ " middleware " => [HasSession::class]], function () {
Route:: get ( " / " , [StoryController::class, " index " ]);
Route:: get ( " /story/{story} " , [StoryController::class, " show " ]);
});您可以使用嵌套路线组与他们的父组相结合。在随后的示例中,“哈斯esseess”中间件应用于"/"和"/story/{story}"路线,而“ hassession”,“ hassession”,isauth和log'中间人都将其应用于其他路线:
Route:: group ([ " middleware " => [HasSession::class]], function () {
Route:: get ( " / " , [StoryController::class, " index " ]);
Route:: get ( " /story/{story} " , [StoryController::class, " show " ]);
Route:: group ([ " middleware " => [IsAuth::class, Log::class]], function () {
Route:: get ( " /compose " , [StoryController::class, " compose " ]);
Route:: post ( " /compose " , [StoryController::class, " create " ]);
});
});在元素PHP框架中,直接从路线和控制器直接返回整个HTML文档字符串是不切实际的。视图提供了一种将所有HTML放入单独文件中的方便方法。
视图在将控制器/应用程序逻辑与演示文稿关注分开,并存储在app/views目录中。这些用PHP编写的视图文件封装了标记。考虑一个观点的基本示例:
<html>
<body>
<h1>Hello, <?= $ name ?> </h1>
</body>
</html>如果此视图存储在app/views/Welcome.php中,则可以使用路线中的全局view助手返回:
Route:: get ( ' / ' , function () {
return view ( ' Welcome ' , [ ' name ' => ' Ahmed ' ]);
});第一个参数传递给view助手”,对应于resources/views目录中的视图文件的名称。第二个参数可以是传递到视图的一系列键值对。例如,在上述代码中, $name将直接访问并包含“ ahmed”值。
也可以使用CoreViewView类上make静态方法返回视图:
Route:: get ( ' / ' , function () {
return View:: make ( " Post " , $ params );
});视图可以嵌套在app/views目录的子目录中。 “点”表示法可用于引用嵌套视图。例如,如果您的视图存储在app/views/layouts/MainLayout.php中,则可以从类似的路由/控制器返回它:
return view ( ' layouts.MainLayout ' , $ data );Elemental提供了一种方便的方法,可以在多个视图中维护相同的布局,从而减少代码重复。布局本身是一个包含占位符{{ content }}的视图文件。当使用布局返回视图时,将视图放在布局内容中来编译最终视图。
Elemental提供了一种方便的方法,可以在多个视图中维护相同的布局,从而减少代码重复。布局是一个视图文件,它包含了指定的占位符,用{{ content }}表示。当使用特定布局返回视图时,将通过将视图的内容嵌入布局中的指定占位符中来实现。这种方法通过集中共同的布局元素来简化观点的组织并增强代码可维护性。
以下是一个基本示例:
<!DOCTYPE html >
< html lang =" en " >
< head >
<!-- Head content -->
</ head >
< body >
< ?= component("components.Navbar") ? >
< div style =" min-height: calc(100vh - 140px); " >
{{ content }}
</ div >
</ body >
</ html >可以以这样的布局返回视图:
public function compose ()
{
return view ( " Compose " )-> withLayout ( " layouts.DashboardLayout " );
}Elemental提供了一种有力的方法来制作观点。每个视图本质上都是一个组件,任何视图都可以从其他组件中组装出来。这是一部构图的交响曲,每件作品都有助于创造一个和谐而充满活力的整体。
示例组件文件( views/components/Logo.php ):
<a class="logo" href="/ " >
<span class= " logo-img">
<img src="logo.png" class ="logo-text">
LOGO
</span>
</a>该组件可以在任何其他视图文件中使用。例如,在views/Login.php中:
<div>
<?= component ( " components.Logo " ) ?>
<p>Welcome Back!</p>
<!-- Other login form elements -->
</div>因此,Elemental赋予您布局和组件构造的能力,从而使您可以通过自上而下和自下而上的方法来构成视图。这种灵活性使无缝的融合可以毫不费力地混合并结合元素,以制作出优雅而精致的用户界面。
在现代Web应用程序中,数据库交互是一个基本方面。 Elemental旨在在各种支持的数据库中无缝地简化这种交互,从而利用PHP PDO的固有功能。使用Elemental,您可以灵活地使用CoreDatabaseDatabase类执行任何复杂的查询或事务。
Elemental提供了一个强大的对象粘合映射器(ORM),该映射器(ORM)有效地抽象了许多复杂性,证明对大多数数据库查询是无价的。但是, CoreDatabaseDatabase可用于运行更高级的SQL查询。
您的Elemental应用程序的所有配置都位于您应用程序的app/config/config.php配置文件中。在这里,您可以定义所有数据库连接,并指定默认情况下应使用哪个连接。该文件中的大多数配置选项都由应用程序环境变量的值驱动。
您的Elemental应用程序的所有配置都位于您应用程序的app/config/config.php配置文件中。在这里,您可以定义所有数据库连接,并指定默认情况下应使用哪个连接。该文件中的大多数配置选项都由应用程序环境变量的值驱动。
<?php
return [
" db " => [
" driver " => getenv ( " DB_DRIVER " ) ?? " mysql " ,
" host " => getenv ( " DB_HOST " ) ?? $ _SERVER [ ' SERVER_ADDR ' ],
" port " => getenv ( " DB_PORT " ) ?? " 3306 " ,
" database " => getenv ( " DB_DATABASE " ) ?? " elemental " ,
" username " => getenv ( " DB_USERNAME " ) ?? " root " ,
" password " => getenv ( " DB_PASSWORD " ) ?? "" ,
],
]; Elemental使用PDO作为基础数据库处理类。所有PDO功能都直接在CoreDatabaseDatabase类中可用。您可以将CoreDatabaseDatabase的实例注入任何构造函数或控制器方法来调用PDO方法。为MySQL数据库设置了elemental的默认配置,但是您可以更改配置文件中的驱动程序。
这是通过Database实例运行查询的一个示例:
public function tokens ( Database $ db ) {
$ user_id = 1 ;
$ sql = " SELECT * FROM access_tokens WHERE user_id = :user_id " ;
$ stmt = $ db -> prepare ( $ sql );
$ stmt -> bindValue ( " :user_id " , $ user_id );
$ stmt -> execute ();
$ tokens = $ stmt -> fetchAll ();
}有关PDO的更多信息,您可以参考PHP的PDO文档
Elemental包括一个定制的对象键合映射器(ORM),它使与数据库进行互动令人愉快。使用ORM时,每个数据库表具有相应的“模型”,用于与该表进行交互。除了从数据库表中检索记录外,模型还允许您从表中插入,更新和删除记录。
模型存在于app/models目录中,并扩展了CoreModelModel类。您可以使用build:model candle命令来生成新模型。
php candle build:model Post build:model命令生成的模型将放置在app/Models目录中。一个非常基本的模型具有以下结构:
<?php
namespace App Models ;
use Core Model Model ;
class Post extends Model
{
// ...
}表名:按照惯例,除非明确指定另一个名称,否则“蛇案”的复数名称将用作表名。因此,在这种情况下,Elemental将假设Post模型存储在posts表中。
您可以通过在模型上定义tableName属性手动指定模型的表名:
<?php
namespace App Models ;
use Core Model Model ;
class Post extends Model
{
protected $ tableName = ' elemental_posts ' ;
}主键:
Elemental还将假设每个模型的相应数据库表具有一个名为id的主键列。如有必要,您可以在模型上定义一个受保护的$primaryKey属性,以指定用作模型主要密钥的不同列:
<?php
namespace App Models ;
use Core Model Model ;
class Post extends Model
{
protected $ primaryKey = ' elemental_id ' ;
}您可以将每个模型视为强大的查询构建器,允许您流利地查询与该模型关联的数据库表。
该模型的all方法将从模型关联的数据库表中检索所有记录:
use App Models Story ;
foreach (Story:: all () as $ story ) {
echo $ story [ " content " ];
}默认情况下,获取的记录表示为数组。但是,您可以传递一个模式参数,该参数控制每个记录的表示方式。模式参数采用任何PDO提取模式。例如,
use App Models Story ;
foreach (Story:: all () as $ story ) {
echo $ story -> content ;
}allWhere方法是模型中强大的抽象,允许执行复杂的查询。此方法采用三个参数: conditions , options和fetchMode 。
public static function allWhere( array $ conditions , array $ options = [], int $ fetchMode = PDO :: FETCH_ASSOC )条件: conditions参数是必须满足的条款的数组才能获取。每个条件可以是[key => value]对,也可以是[key => [operator, value]]对。
key对应于表中的特定列。[key => value]的形式,则默认运算符为=并且value是该列内的数据记录。[key => [operator, value]] ,则可以为每个条件指定操作员。受支持的运营商是:['=', '!=', '<', '>', '<=', '>=', 'LIKE', 'IS NULL', 'IS NOT NULL'] 。选项: options参数是一个数组,该数组确定其他查询参数,例如order by , limit等。选项参数中支持的构造包括:
"orderBy""limit""offset""sortDir" fetchmode: fetchMode参数控制每个获取的记录的表示方式。模式参数采用任何PDO提取模式:
PDO::FETCH_ASSOCPDO::FETCH_NUMPDO::FETCH_BOTHPDO::FETCH_OBJPDO::FETCH_CLASSPDO::FETCH_INTOPDO::FETCH_LAZYPDO::FETCH_KEY_PAIR一个例子将使情况更加清楚:
use Core Request Request ;
class StoryController {
const PAGE_SIZE = 10 ;
public function index ( Request $ request )
{
$ search = $ request -> search ;
$ categoryId = $ request -> category_id ;
$ sortBy = $ request -> sort_by ; // ASC or DESC, Default = ASC
$ page = $ request -> page ;
$ orderBy = $ request -> order_by ;
return Story:: allWhere (
[
" category_id " => $ categoryId ,
" title " => [ " LIKE " , " % $ search $ " ],
],
[
" limit " => static :: PAGE_SIZE ,
" orderBy " => $ orderBy ,
" sortDir " => $ sortBy ,
" offset " => ( $ page - 1 ) * static :: PAGE_SIZE ,
],
PDO :: FETCH_OBJ
);
}
}除了检索与给定查询相匹配的所有记录外,您还可以使用find和where检索单个记录。这些方法没有返回一系列记录,而是返回单个模型实例:
查找:这将获取与表的主键匹配的第一个记录。
$ flight = Story:: find ( 1 );地点:其中的方法采取了一系列条件,这些条件必须满足以获取的记录。每个条件可以是[key => value]对,也可以是[key => [operator, value]]对。
key对应于表中的特定列。[key => value]的形式,则默认运算符为=并且value是该列内的数据记录。[key => [operator, value]] , you can specify the operator for each condition. The supported operators are:['=', '!=', '<', '>', '<=', '>=', 'LIKE', 'IS NULL', 'IS NOT NULL'] .例如
$ user = User:: where ([ " email " => $ email ]);
$ liked = Like:: where ([ " user_id " => $ user -> id , " story_id " => $ story_id ]);To insert a new record into the database, you can instantiate a new model instance and set attributes on the model. Then, call the save method on the model instance:
<?php
namespace App Controllers ;
use App Models Story ;
use Core Request Request ;
class StoryController
{
public function store ( Request $ request )
{
$ story = new Story ;
$ story -> name = $ request -> name ;
$ story -> save ();
return redirect ( ' /story ' );
}
} In this example, we assign the name field from the incoming HTTP request to the name attribute of the AppModelsStory model instance. When we call the save method, a record will be inserted into the database. The model's created_at timestamp will automatically be set when the save method is called, so there is no need to set it manually.
Alternatively, you may use the static create method to "save" a new model using a single PHP statement. The inserted model instance will be returned to you by the create method:
use App Models Story ;
$ story = Story:: create ([
' name ' => ' A tale of elemental magic ' ,
]);The save method may also be used to update models that already exist in the database. To update a model, you should retrieve it and set any attributes you wish to update. Then, you should call the model's save method.
use App Models Story ;
$ story = Story:: find ( 10 );
$ story -> name = ' An elemental tale of magic ' ;
$ story -> save (); Alternatively, you may use the static update method to update a model instance. The first argument is the id of the model, and the second argument needs to be the array of column value pair.
use App Models Story ;
$ story = Story:: update ( 10 , [ " name " => " A tale " , " content " => " Once upon a time .... " ]);To delete a model, you may call the destroy method on the model instance:
use App Models Story ;
$ story = Story:: find ( 12 );
$ story -> destroy (); However, if you know the primary key of the model, you may delete the model without explicitly retrieving it by calling the delete method. The id of the deleted record is returned.
use App Models Story ;
Story:: delete ( 12 );You may call the data method on the model to retrieve all the attributes of a modal instance in an array form.
$ user = User:: find ( 10 );
$ user_data = $ user -> data (); Candle is the command line engine of the Elemental. Candle exists at the root of your application as the candle script and provides a number of helpful commands designed to aid you in the development process of your application. To view a list of all available Candle commands, you may use the help command:
php candle helpThis will also display the custom commands that you may have created yourself.
By now, you must have already ignited the the Elemental's candle to run your app. This ignite command serves the app at the IP Address 127.0.0.1, searching for a free port starting from 8000. If Port 8000 is occupied, Elemental automatically attempts to bind to the next available port (eg, 8001) and so forth.
php candle igniteYou have the flexibility to customize the server setup according to your requirements.
Custom Host Specify a specific IP address using the --host argument.例如:
php candle ingite --host=192.168.1.10 Custom Port If you prefer binding to a specific port, use the --port argument:
php candle ingite --port=8080 To serve your application at a custom IP and port simultaneously, provide both the --host and --port arguments:
php candle ingite --host=192.168.1.10 --port=8080 The --host and --port arguments can be placed in any order.
To obtain a comprehensive view of all registered routes within your application, utilize the route:list command provided by Candle:
bash
php candle route:list You can use the Candle build command to generate files for your models, controllers, middleware and commands.
To create a model, execute the following command:
php candle build:model Story This command will generate a file named Story.php within the appmodels directory, containing the Story class.
For generating a controller, the build command is similarly employed:
php candle build:controller StoryController Executing this command will generate a file named StoryController.php in the appcontrollers directory, featuring the MyController class.
To generate a middleware, utilize the build command as follows:
php candle build:middleware HasSession This will create a file named HasSession.php within the appmiddleware directory, housing the handle method.
For command generation, execute the build command with the appropriate arguments:
php candle build:command Migration Executing this command will generate a file named Migration.php in the appcommands directory, containing the Migration class and the handle method.
Generating custom commands is where the Candle's power can be experienced. Commands are stored in the app/commands directory, and it's essential to load them inside the array returned in appcommandsCommands.php for proper registration within the app.
After generating a command, define values for the key and description properties of the class. The key is used as the argument for the command, while description will be displayed in the help screen. The handle method will be called when the command is executed, and you can place your command logic in this method.
You can type-hint any dependencies required for your command handling. Elemental's DI Container will automatically inject all dependencies type-hinted in the handle method's signature.
Let's take a look at an example command:
<?php
namespace App Commands ;
use App Models User ;
use App Service MailService ;
use Core Console Command ;
class SendEmails extends Command
{
protected $ key = ' mail:send ' ;
protected $ description = ' Send mails to all users ' ;
public function handle ( MailService $ mailService ): void
{
$ mailService -> send (User:: all ());
}
}To execute the command in the command line:
php candle mail:send You can use Elemental's CoreConsoleCommander to retrieve any inputs passed through the command line. The CoreConsoleCommander provides a method named getArgs that returns an array of inputs passed from the command line. The Commander instance can be type-hinted through the handler method and used as required.
A concrete example will make it clear:
<?php
namespace App Commands ;
use Core Console Command ;
use Core Console Commander ;
class Migration extends Command
{
protected $ key = " migrate " ;
protected $ description = " Custom migration handler. " ;
private $ commander ;
public function handle ( Commander $ commander , Database $ db )
{
$ args = $ commander -> getArgs ();
if (! isset ( $ args [ 1 ])) {
$ this -> up ();
return ;
}
switch ( $ args [ 1 ]) {
case " fresh " :
$ this -> downThenUp ();
break ;
case " delete " :
$ this -> down ();
break ;
default :
$ this -> up ();
}
}
public function up ()
{
$ sql = " CREATE TABLE IF NOT EXISTS users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
bio TEXT,
image VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) " ;
try {
$ db -> exec ( $ sql );
console_log ( " Table 'users' created successfully! " );
} catch ( PDOException $ e ) {
console_log ( " Table creation error: " . $ e -> getMessage ());
}
}
public function down ()
{
$ sql = " DROP TABLE IF EXISTS users " ;
try {
$ db -> exec ( $ sql );
console_log ( " Table 'users' deleted successfully! " );
} catch ( PDOException $ e ) {
console_log ( " Table deletion error: " . $ e -> getMessage ());
}
}
public function downThenUp ()
{
$ this -> down ();
$ this -> up ();
}
}It is recommended to type-hint dependencies inside the handle method as opposed to inside the constructor of the command class.
To execute these migration commands in the command line:
php candle migrate
php candle migrate fresh
php candle migrate deleteAs you can see, generating commands are very powerful and can be helpful to achieve a variety of functionalities. Here, a custom migration handler has been built. You can expand and organize the above structure or create a custom Migration Service that can handle your migration logic.
Commands can also be used for handling task scheduling. You may create a command that executes some logic, and then pass the command to your operating systems CRON handler.
Elemental includes a variety of global "helper" PHP functions. You can use these functions in any way that is convenient to you.
The app function returns the Application instance:
$ app = app ();This is pretty useful when you want to register your own services as well as resolve any framework or custom service.
app ()-> bind (CustomService::class, function () {
return new CustomService ( new anotherService ());
});
$ service = app ()- make (CustomService::class); The dump function dumps the variable passed as the first argument. You can also pass an additional second argument that can serve as the identifier on screen:
dump ( $ value );
dump ( $ user , " user " ); The dd function dumps the given variable and ends the execution of the script:
dd ( $ value );
dd ( $ user , " user " ); The console_log function serves as a unique tool for logging variables, distinct from the dump function. Notably, it doesn't return output to the browser; instead, it directs information to the console initiated by the script. You can pass any variable number of arguments to the console_log function.
console_log ( $ value );
console_log ( $ user , $ post , $ image , $ comment ); The router function returns the returns the Router instance.
The view function is used to return a view from the controller method:
return view ( ' Login ' ); The component function is used to return a view as a component to be used inside another view:
<body>
<?= component ( " Logo " ) ?>
//...
</body> The redirect function returns a redirect HTTP response and is used to redirect to any other route.
return redirect ( ' /home ' );Elemental provides a convenient way to handle all the exceptions thrown by the app.
The handle method of AppExceptionsHandler class is where all exceptions thrown by your application pass through before being rendered to the user. By default, exceptions thrown by the app will be formatted, and a structured response will be sent back to the browser. However, inside the handle method, you can intercept any exception and perform custom logic before the response is sent back.
You can even send back a custom view or a response.
<?php
namespace App Exceptions ;
use Core Exception ExceptionHandler ;
class Handler extends ExceptionHandler
{
public function handle ( $ e )
{
// Perform some processing here
// You can customize the handling of exceptions based on your requirements
}
}Elemental has defined some specific exception classes by default:
AppExceptionModelNotFoundExceptionRouteNotFoundExceptionRouterExceptionViewNotFoundException If you need to handle different types of exceptions in different ways, you can modify the handle method accordingly:
<?php
class Handler extends ExceptionHandler
{
public function handle ( $ e )
{
if ( $ e instanceof ModelNotFoundException || $ e instanceof RouteNotFoundException) {
return view ( " 404 " )-> withLayout ( " layouts.DashboardLayout " );
}
if ( $ e instanceof ViewNotFoundException) {
return view ( " Home " );
}
// Handle other specific exceptions as needed
}
} You are free to create your own exception classes by extending from the base Exception class, which can then be handled as required.
Feel free to customize the handle method based on your application's specific needs.
All configuration settings for the application are centralized in the appconfigconfig.php file. These configurations cover various aspects such as database connection information and other core settings essential for your app.
To cater to different environments where the application might run, a .env.example file is provided in the root directory. This file outlines common environment variables that can be configured. If you are working in a team, it's recommended to include the .env.example file with placeholder values. This makes it clear to other developers which environment variables are required to run the application.
When your application receives a request, all the variables listed in the .env file will be loaded into the $_ENV PHP super-global. You can then use the getenv function to retrieve values from these variables in your configuration files.
$ appName = getenv ( " APP_NAME " ); To access configuration values, you can use type-hinting and inject the CoreConfigConfig class into your constructors, controller methods, or route closures.
use Core Config Config ;
class YourClass {
public function __construct ( Config $ config ) {
$ driver = $ config -> db [ " driver " ];
$ host = $ config -> db [ " host " ];
$ port = $ config -> db [ " port " ];
}
// Your other methods or code here
}By doing this, you have a clean and organized way to retrieve configuration values within your application.
This approach keeps your configuration centralized and allows for easy changes based on the environment. It also promotes a clean and maintainable codebase.
Elemental introduces a Facade system inspired by Laravel, providing a convenient and expressive static interface to classes within the application's Dependency Injection (DI) container. Facades act as static proxies to classes in the service container, offering a balance between a concise syntax and the testability and flexibility of traditional static methods.
In Elemental, the CoreFacadeRoute serves as a Facade, offering a static interface to the application's Router instance enabling you to use it like this in the routes.php file:
// routes.php
<?php
use Core Facade Route ;
Route:: get ( " /register " , [AuthController::class, " showRegister " ]);
Route:: get ( " /login " , [AuthController::class, " showLogin " ]);
Route:: get ( " /logout " , [AuthController::class, " logout " ]);
Route:: post ( " /register " , [AuthController::class, " register " ]);To create a custom Facade for any class, follow these steps:
FacadeClass that extends the CoreFacadeFacade class.getFacadeAccessor , returning the class string for the associated instance in the DI container. Here's an example of creating a PaymentGateway Facade:
<?php
use Core Facade Facade ;
use Core Services PaymentGateway ;
class PaymentGatewayFacade extends Facade
{
protected static function getFacadeAccessor ()
{
return PaymentGateway::class;
}
} Now, you can access the instance methods of your custom class by calling static methods on the corresponding FacadeClass .
LARAVEL is Magic . Like any unsuspecting Muggle, it's enchantments terrify you. Until one fine day, you dare to pick up the wand and start waving it. Then, you fall in love with it.
The Elemental framework is open-sourced software licensed under the MIT License.
All contributions are welcome. Please create an issue first for any feature request or bug. Then fork the repository, create a branch and make any changes to fix the bug or add the feature and create a pull request.就是这样!谢谢!
For bug reports, feature requests, or general questions, please use the issue tracker.