Project modules

Introduction


This application is based on the skeleton application using the Laminas MVC layer and module systems, on top of Rest and Rpc rhubbit core modules

Required library

Media setting

Install ffmpeg to manage video thumb auto generate process

$ sudo apt-get install ffmpeg

Install dependency

Install dependency modules with:

$ composer install

Download required npm package with

$ npm install

Generate spec

OpenApi spec / redoc

Generate your web services documentation in /public/doc/api.html from open api spec with:

$ npm run "Bundle HTML spec"


Module spec / pandoc

Download package following instruction at https://pandoc.org/installing.html

Generate your modules documentation in /public/doc/modules.html from all module *.md files with:

$ npm run "Modules spec"

Config params

This is the global application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'log' => true,
    'version' => 'x.y.z',
    'logpath' => '/var/www/your-app-folder/log',
    'log_level' => [
        'cli' => \Monolog\Level::Debug,
        'listener' => \Monolog\Level::Debug,
        'web' => \Monolog\Level::Debug,
    ],
    'clipath' => '/var/www/your-app-folder/vendor/bin/laminas',
    
    //...
];
Parameter Description
log If set to TRUE application log are enabled, otherwise every app log will be ignored
version Application version number express with semver syntax
logpath folder where log will be saved
log_level log criteria in use foreach of the supported area
log_level.cli command line interface log level saved in [logpath]/cli.log file
log_level.listener listener log level saved in [logpath]/listener.log file
log_level.web web log level saved in [logpath]/web.log file
clipath command line interface base path, used when application try to exec(/*cli-command*/)



Database


This module implement standard Database interface based on Propel ORM

Config params

This is the module application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'database' => [
        'enable_log' => true,
        'logfilepath' =>  '/var/www/your-app-folder/log/database.log',
        'connections' => [
            'YourDB' => [
                'dsn' => 'mysql:host=localhost;dbname=YourDB;charset=utf8',
                'host' => 'localhost',
                'usr' => 'db-user',
                'pwd' => 'xxxxxxxxx',
                'dbname' => 'YourDB',
            ],
        ],
    ],
    
    //...
];
Parameter Description
enable_log If set to TRUE every query executed via propel ORM will be saved in [logfilepath] file
logfilepath absolute file path where log will be saved
connections list of supported db connection
connections.[dbname] connection params array of a specific db
connections.[dbname].dns dns connection param of a specific db
connections.[dbname].host host connection param of a specific db
connections.[dbname].usr username connection param of a specific db
connections.[dbname].pwd password connection param of a specific db
connections.[dbname].dbname database name of a specific db

Initialize DB

1 - Move from <project_folder> to database module folder:

$ cd module/Database

2 - run the following command to generate file in /generated-conf, /generated-sql, /generated-classes folders:

$ ../../vendor/propel/propel/bin/propel config:convert 
$ ../../vendor/propel/propel/bin/propel sql:build
$ ../../vendor/propel/propel/bin/propel model:build

3 - update autoload including /generated-classes reference with:

$ composer update

Diff / Migrate

1 - Move from <project_folder> to database module folder:

$ cd module/Database

2 - run the following commands to generate migration:

$ ../../vendor/propel/propel/bin/propel diff 
$ ../../vendor/propel/propel/bin/propel migrate
$ ../../vendor/propel/propel/bin/propel model:build

3 - update autoload including /generated-classes reference with:

$ composer update



Notification


This module implement Notification lifecycle sending:

  • email message with phpmailer/phpmailer over SMTP protocol
  • push notification with kreait/firebase-php over firebase google service

You can get phpmailer and firebase-php with composer.

Config params

This is the module application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'notification' => [
        'push' => [
          'secret' => APPLICATION_ROOT . '/secret/firebase.json',
        ],
        'email' => [
            'smtp_options' => [
                'host' => 'smtps.aruba.it',
                'port' => 465,
                'username' => 'info@storeoverview.it',
                'password' => 'ftvKC9nhnFPtw^rC',
                'encryption' => PHPMailer::ENCRYPTION_SMTPS,
                'from_address' => 'info@storeoverview.it',
                'from_name' => 'SOV Dev',
                'extra' => [ //extra email params
                    'key_1' => "value 1",
                    'key_2' => "value 2"
                ]
            ],
        ],
    ],
    
    //...
];
Parameter Description
push.secret firebase configuration json
email.smtp_options.host SMTP server url
email.smtp_options.port SMTP server port
email.smtp_options.username SMTP account username
email.smtp_options.password SMTP account username
email.smtp_options.encryption SMTP connection encryption
email.smtp_options.from_address SMTP sender address used in sent email
email.smtp_options.from_name SMTP sender name used in sent email
email.smtp_options.extra array of key / value couple passed to every message template to enable advanced message customization

RestIdentity


This module implement standard Database interface based on Propel ORM

Config params

This is the module application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'client' => [
        'base_url' => 'https://www.your-app-frontend.com',
        'confirm_url' => 'https://www.your-app-frontend.com/confirm'
    ],
    
    //...
    'security' => [
        'bundle' => 'api.project_name',
        'secret' => 'xxxxxxxxxxxx',
        'ttl' => 1800,
        'refresh_ttl' => '+5 day',
        'confirm_ttl' => '+1 hour',
        'default_role_id' => 3,
        'admin_role_id' => [1, 2],
        'enable_client_log' => true,
        'cc_user_invitation_to' => 'admin@domain.com',
    ],
    
    //...
];
Parameter Description
client main client configuration if required
client.base_url homepage url of the main client
client.confirm_url confirm account page url of the main client
security.bundle unique application bundle used to ensure JWT token by project
security.secret secret key to enforce JWT token security
security.ttl JWT token time to live in seconds
security.refresh_ttl refresh token time to live in a format accepted by strtotime()
security.confirm_ttl identity confirmation code time to live in a format accepted by strtotime()
security.default_role_id default role id assigned to new users
security.admin_role_id list of role id enabled as admin
security.enable_client_log flag used to enable DeviceLogController used to support client to log error on server with the aim to support production client error debug
security.cc_user_invitation_to add this setting optional to send a cc copy of each user invitation email to the specified address

RestMedia


Module built to implementent standard RESTFul API and controller interface to manage runtime uploaded application media

Required library

Install ffmpeg to manage video frame extracting process

$ sudo apt-get install ffmpeg

Config params

This is the module application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'media' => [
        'placeholder' => '/var/www/your-app-folder/public/img/img-placeholder.png',
        'cdn' => [
            'baseurl' => 'https://www.your-cdn.com',
            'basepath'=> '../your-app-cdn/public',
            'absolutepath'=> '/var/www/your-app-cdn/public',
        ],
        'entity' => [ 
            // list of entity managed by RestMedia module
        ]
    ]
    
    //...
];
Parameter Description
placeholder absolute path of placeholder file used if a media can’t be found
cdn local application CDN config params
cdn.baseurl public baseurl of the local CDN used from RestMedia module to create media public link
cdn.basepath relative base path of the local CDN used from RestMedia module to locate uploaded media folder
cdn.absolutepath absolute base path of the local CDN used from RestMedia module to locate uploaded media folder
entity list of entity with related media managed by RestMedia module stored in local CDN with path like /{entity_type}/{eid}/media/...


Entity config

Foreach entity with related media managed by RestMedia you have to define the module behavior via a config params array put under reserved key identifying the entity name, key labeled EntityType in the following example (in real project could be User, Product, MediaGallery, …)

return [
    //...
    
    'media' => [
        //...
        'entity' => [ // list of entity managed by RestMedia module
            'EntityName' => [ // entity name
                'max_file_size' => '4MB',
                'mime_types' => \RestMedia\Filter\MediaFilter::getStandardImageTypeAccepted(), // list of supported mime type for entity
                'resize' => [
                    'thumb' => [
                        'mode' => \RestMedia\Model\MediaModel::CROP_MODE,
                        'width' => 150,
                        'height' => 150,
                    ],
                    //...
                ],
                'video_screenshot_second' => 0, //video seconds to generate thumb
                'video_screenshot_extension' => "png",
                'video_screenshot' => [
                    'thumb' => [
                        'mode' => \RestMedia\Model\MediaModel::CROP_MODE,
                        'width' => 150,
                        'height' => 150,
                    ],
                    //...
                ],
            ],
            // ...
        ]
    ]
    
    //...
];
Parameter Description
max_file_size max upload file size accepted
mime_types list of mime types accepted: you can set an explicit static list or use one of the \RestMedia\Filter\MediaFilter helper methods with common mime types list
resize list of resize criteria automatically applied to every uploaded image (in the example there is only thumb criteria but you can add all match criteria you want).

Resized media will stored in folder with criteria name (in the example /{entityname}/{eid}/media/thumb
resize.[criteria] your custom resize criteria name, thumb in the example
resize.[criteria].mode resize mode, accepted values are: CROP/SCALE/RESIZE
resize.[criteria].width the resized image width
resize.[criteria].height the resized image height
video_screenshot_second param used with uploaded videos to define the instant to get screenshot
video_screenshot_extension param used with uploaded videos to define screenshot image file extension (png, jpg, …)
video_screenshot param used with uploaded videos to define screenshot image resize criteria (you can add all match criteria you want)
video_screenshot.[criteria] your custom resize criteria name, thumb in the example, this config object use the generic resize criteria (see resize.[criteria])


How to use

RestMedia is a module based on Rest and RestIdentity modules and can be used in two different ways:

  1. via /media_gallery/[:id] and /media_gallery/[:id]/media/[:mid] standard RESTFul interface
  2. extending MediaController with a custom subclass and relate application routing


RESTFul interface

Using standard RESTFul interface you will have access to classic media gallery system. In the case you have to only define MediaGallery entity config following specification in this document, then you will have access to:

  • /media_gallery/[:id] RESTFul interface to manage runtime a simple MediaGallery entity used as container of the uploaded medium
  • /media_gallery/[:id]/media/[:mid] RESTFul interface to upload and manage media uploaded inside the MediaGallery identified by :id

You can create multiple media gallery inside your application and, if you like it, related entity from different modules to it via :id unique identifier.


Extend MediaController

Extending MediaController, a subclass of AdvancedRestfulController exposed by Rest module, you can create your custom media gallery controller to fully manage business logic behind your uploading criteria.

Create your MediaGallery subclass is simple, you only need a controller like this:

    class ProductMediaController extends MediaController
    {
        private $ownerId = 0;
        private $entityId = 0;
    
        public function onDispatch(MvcEvent $e)
        {
            $this->ownerId = //set your media owner id
            $this->entityId = //set the id of main entity relate to the media
    
            return parent::onDispatch($e);
        }
    
    
        /*********************************************
         * MediaController subclass abstract methods *
         *********************************************/
    
        // return the media owner user id
        public function getOwnerId(): ?int
        {
            return $this->ownerId;
        }
    
        // return the name of the entity type related to the media
        public function getEntityType(): string
        {
            return ProductEntity::ENTITY_NAME;
        }
    
        // return the unique id of the entity type related to the media
        public function getEntityId(): int
        {
            return $this->entityId;
        }
        
        
        /*********************************************************
         * MediaController subclass optional override of methods *
         *********************************************************/
        
        // Optionally set a subclass of MediaModel as model used by
        // MediaController to implements advanced customization
        public function getMediaModel(): ?MediaModel
        {
            return new CustomMediaModel(...)
        }
    }

Then you need to init register your controller following generic Rest module criteria and defining custom related routes like this:

return [
    'router' => [
        'routes' => [
            //...
            '/api/v1/product/{id}/media' => [
                'type' => Segment::class,
                'options' => [
                    'route' => '/api/v1/product/:pid/media[/]',
                    'constraints' => [
                        'pid' => '[1-9]\d*'
                    ],
                    'defaults' => [
                        'controller' => 'RestProduct\Controller\ProductMedia'
                    ]
                ],
            ],
            '/api/v1/product/{id}/media/{id}' => [
                'type' => Segment::class,
                'options' => [
                    'route' => '/api/v1/product/:pid/media/:id[/]',
                    'constraints' => [
                        'pid' => '[1-9]\d*',
                        'id' => '[1-9]\d*'
                    ],
                    'defaults' => [
                        'controller' => 'RestProduct\Controller\ProductMedia'
                    ]
                ],
            ],
        ],
    ],
    'controllers' => [
        'invokables' => [
            'RestProduct\Controller\ProductMedia' => Controller\ProductMediaController::class,
            //...
        ],
    ],
    //...
];



Rest


This module contains standard advanced controller to support RESTFul API development based and validated on open api spec.

Required library

  1. Install module APCU Cache:

    $ sudo apt install php-apcu
    $ sudo apt install php8.2-apcu
  2. Configure APC in php.ini:

    $ vim /etc/php/8.2/apache2/php.ini

    With APC Default configuration:

    extension=apcu.so
    apc.enabled=1
    apc.shm_size=1024M
    apc.max_file_size=10M
    apc.num_files_hint=20000
    apc.user_entries_hint=20000
    apc.ttl=7200
    apc.user_ttl=7200
    apc.gc_ttl=3600
    apc.include_once_override=0
    apc.enable_cli=1

    Then reload apache

    sudo service apache2 restart
  3. Download apcu monitoring page in your web root or dedicate virtual host web root

    $ cd /your/project/root/public
    $ wget https://raw.githubusercontent.com/krakjoe/apcu/master/apc.php

    then set username and password in apc.php

  4. Reset oas_spec entry from apc cache after every open api spec update

    $ curl http://your.domain.xxx/spec-clear.php

Config params

This is the module application config params list.

Set this parameters in local.php|global.php under /config/autoload folder to define application behaviours:

return [
    //...
    
    'debug' => true,
    'oldest_supported_version' => 'x.y.z',
    'spec' => '/var/www/your-app-folder/service-spec/generated-spec/spec.yaml',
    
    //...
];
Parameter Description
debug If set to TRUE when application trigger error/exception JSON response will include full stacktrace
oldest_supported_version oldest supported version number express with semver syntax, if client request older API version via Accept-Version header will be triggered an 406 - Not acceptable response error exception
spec folder where spec yaml file will be saved

Module configuration

All the subclasses of AdvancedRestfulController, if configured, will automatically trigger standard events propagated throughout the application by Laminas and listenable by every application modules via dedicate listeners attached to onBootstrapt event of the module.

To enable your modules and related controllers to trigger and listen RestEvent you have to:

  1. extend your Module class with AbstractRestfulModule

    class Module extends AbstractRestfulModule ...
  2. implement getBundles method of the superclass returning an array key/values with:

    • key: a unique bundle name identifying your module, eg. com.rhubbit.rest.acl

    • values: a list of controller enabled to trigger this kind of events

        'com.rhubbit.rest.acl' => [
           Controller\DeviceController::class,
           Controller\IdentityController::class,
           Controller\IdentityConfirmationController::class
        ]
  3. set the event context name in the onDispatch method of the related controller (typically using the related Entity name)

     public function onDispatch(MvcEvent $e)
     {
         $this->setEventContext(ProductEntity::ENTITY_NAME);
         return parent::onDispatch($e);
     }

Event structure

Every event automatically triggered by AdvancedRestfulController contains: - bundle: unique event name composed by module bundle and context (entity name) related to the controller, eg. com.rhubbit.rest.acl/identity - event: the specific event name, eg. create.pre, update.post, … - target: reference to the object instance triggering the event - params: an array with all the parameters attached by the controller to the event

Standard events

Every AdvancedRestfulController, if added in the array returned by getBundles, will automatically trigger the event matching action managed:

HTTP Event params
GET get.pre the same input of the controller get method
GET get.post the same output that controller get method will return (the Entity)
GET get.error the same error that controller get method will return
GET getList.pre the same input of the controller getList method
GET getList.post the same output that controller getList method will return (the Entity)
GET getList.error the same error that controller getList method will return
POST create.pre the same input of the controller create method
POST create.post the same output that controller create method will return (the Entity)
POST create.error the same error that controller create method will return
PUT update.pre the same input of the controller update method
PUT update.post the same output that controller update method will return (the Entity)
PUT update.error the same error that controller update method will return
DELETE delete.pre the same input of the controller delete method
DELETE delete.post a BaseEntity object with the same input of the controller delete method (Entity at this time was deleted)
DELETE delete.error the same error that controller delete method will return


Event lister

To create a new RestEvent listener you have to:

  1. create a class and implements ListenerAggregateInterface

  2. add this class to the factories list managed by the module in the service_manager block of the controller module.config.php file php 'service_manager' => [ ... 'factories' => [ ... Listener\ProductSyncListener::class => SyncListenerFactory::class, ... ], ],

  3. implement the attach method of the ListenerAggregateInterface

    //
    public function attach(EventManagerInterface $events, $priority = - 100)
    {
        $sharedManager = $events->getSharedManager();
    
        $this->listeners[] = $sharedManager->attach('api.shopcm/product', 'create.pre', array(
            $this,
            'onProductCreating'
        ), $priority);
    
        $this->listeners[] = $sharedManager->attach('api.shopcm/product', 'create.post', array(
            $this,
            'onProductCreated'
        ), $priority);
    
       //...
    }
  4. define callback method related to every attached event, e.g. onProductCreating e onProductCreated

  5. attach this listener in the onBootstrap module event callback

    //register single event listener
    $eventManager = $app->getEventManager();
    $app->getServiceManager()
        ->get(Listener\ProductSyncListener::class)
        ->attach($eventManager);