Application services
While service guarantors and factories are used primarily for the framework's internal services, application services (as the name suggests) demonstrate a possible implementation of DI within the application, and you will find practical examples of their usage in the demo application.
The core concept is that an application built on PHP Jet should consist of application modules. A module does not merely represent a sub-part of the application, but can also act as a service for the system or for other modules.
Let's look at two examples.
First Example: Authentication and Authorization Controller
The first example is the authentication and authorization controller of the authentication and authorization system. It is a service that must implement the Jet\Auth_Controller_Interface interface. This service can be provided by any class, but it is preferable to design the authorization subsystem as an application module that is easily replaceable, portable between projects, and reusable. This is precisely how it is implemented in the sample application. Take a look, for instance, at the Admin.Auth.Controller module and its main class Main:
namespace JetApplicationModule\Admin\Auth\Controller;
class Main extends Application_Module implements Application_Admin_Services_Auth_Controller
{
protected Administrator|false|null $current_user = null;
public function checkCurrentUser(): bool
{
...
}
public function getCurrentUser(): Administrator|false
{
...
}
public function handleLogin(): void
{
...
}
public function logout(): void
{
...
}
public function login( string $username, string $password ): bool
{
...
}
public function loginUser( Auth_User_Interface $user ) : bool
{
...
}
public function getCurrentUserHasPrivilege( string $privilege, mixed $value=null ): bool
{
...
}
public function checkModuleActionAccess( string $module_name, string $action ): bool
{
...
}
public function checkPageAccess( MVC_Page_Interface $page ): bool
{
...
}
}
This implements the service responsible for monitoring user login state and permissions. Besides this module, you can also find Web.Auth.Controller and REST.Auth.Controller in the sample application. They fulfill the same role, but in the context of web or REST server authentication—working with entirely different user/role entities and using distinct login procedures. That makes three distinct implementations of the same system service.
So we have the services ready. Now we must tell the system which service implementation to use. In the demo application, this is configured in base initializers. For example, in Application_Web::init( MVC_Router $router ): void, you will find this snippet:
Auth::setControllerProvider( function() : Application_Web_Services_Auth_Controller {
return Application_Web_Services::AuthController();
} );
Note: In this specific scenario, the service is not instantiated immediately, but only when a component of the application genuinely requires user authentication and authorization (reason: performance; constantly instantiating unneeded objects reduces execution speed). Thus, an anonymous function is registered with the service guarantor instead of a fully created service instance, lazy-instantiating the service only when invoked.
As you can see, invoking the service initialization itself looks like this:
return Application_Web_Services::AuthController();
Thus, no concrete module is initialized directly; instead, the application services subsystem is utilized to locate the application module providing the service. We will discuss the details of service discovery later.
Now let's examine another example, which is less system-oriented and deals directly with inter-module communication.
Second Example: Image Gallery
The sample application includes a lightweight CMS designed solely to showcase development principles in PHP Jet. This CMS works exclusively with image galleries and articles. An article entity can have lead images. For demonstration purposes, these images are managed by image gallery modules. The article modules do not handle image management directly; they focus solely on text content and delegate image management tasks to external module services. This is an ideal and necessary architecture for building large applications. Let's see how it works in practice.
First, we define the interface for this administration service:
namespace JetApplication;
use Jet\Form_Field;
use Jet\Application_Service_MetaInfo;
#[Application_Service_MetaInfo(
group: Application_Service_Admin::GROUP,
is_mandatory: false,
name: 'Image gallery manager',
description: ''
)]
interface Application_Service_Admin_ImageManager
{
public function includeSelectImageDialog() : string;
public function renderSelectImageWidget( Form_Field $form_field ) : string;
}
Notice that the service interface in the demo application defines metadata via the Application_Service_MetaInfo attribute. What is this useful for? It enables building features such as a service configurator. In large application systems, you can configure through an admin user interface which specific module (or modules) fulfills a given service. While the sample application does not (yet) feature an administrative UI for this, service providers are configured via configuration files. This is the first component of systematized application service management.
Additionally, a service manager exists in the application. Let's look at the manager responsible for administration services:
namespace JetApplication;
use Jet\Application_Module;
use Jet\Application_Service_List;
use Jet\SysConf_Path;
class Application_Service_Admin
{
public const GROUP = 'Admin';
protected static ?Application_Service_List $list = null;
public static function getList(): Application_Service_List
{
if(!static::$list) {
static::$list = new Application_Service_List(
SysConf_Path::getConfig().'services/admin.php',
static::GROUP
);
}
return static::$list;
}
public static function ImageManager() : null|Application_Module|Application_Service_Admin_ImageManager
{
return static::getList()->get( Application_Service_Admin_ImageManager::class );
}
public static function AuthController() : Application_Module|Application_Service_Admin_Auth_Controller
{
return static::getList()->get( Application_Service_Admin_Auth_Controller::class );
}
public static function AuthLoginModule() : Application_Module|Application_Service_Admin_Auth_LoginModule
{
return static::getList()->get( Application_Service_Admin_Auth_LoginModule::class );
}
public static function Logger() : null|Application_Module|Application_Service_Admin_Logger
{
return static::getList()->get( Application_Service_Admin_Logger::class );
}
}
Notice that this straightforward class utilizes the Jet\Application_Service_List class to manage the list of services. It handles all routine operations related to service configuration. A specific service manager, such as this sample JetApplication\Application_Service_Admin, simply exposes a clean and accessible facade for interacting with services in that application segment.
Consuming the service is then very straightforward. The sample Content.Articles.Admin module needs an image selection tool from the gallery when editing articles. It simply uses the service in its edit.phtml view:
namespace JetApplicationModule\Content\Articles\Admin;
use JetApplication\Application_Service_Admin;
...
$image_manager = Application_Service_Admin::ImageManager();
?>
<?= $image_manager?->includeSelectImageDialog(); ?>
...
<?= $form->start(); ?>
...
<?php if($image_manager):
$image_field = $form->field($prefix . 'title_image');
....
echo $image_manager->renderSelectImageWidget( $image_field );
....
endif; ?>
....
<?= $form->end(); ?>
The article management module has no direct coupling with image management code, yet seamlessly incorporates the image gallery. If a developer needs a different UI/UX for image management, the underlying module can simply be swapped or rewritten. Like building blocks :-)
Third Example: Logger
You may have noticed that in the sample service manager JetApplication\Application_Service_Admin there are additional services besides the image gallery shown above. For instance, there is a Logger service.
It is configured by registering the module implementation in the base initializer:
namespace JetApplication;
use Jet\Logger;
....
class Application_Admin
{
....
public static function init( MVC_Router $router ): void
{
Logger::setLogger( Application_Service_Admin::Logger() );
....
}
}
This gives us another modular building block. Logging in the administration section will be handled by a specific application module, and if that implementation is no longer suitable, it can be swapped out or rewritten entirely. What's more, which module is selected can be defined via configuration. Take a look at the configuration file -application/config/services/admin.php, as well as other configuration files in the sample application.