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.

Previous chapter
Jet\Factory_InputCatcher
Next chapter
Jet\Application_Service_List