Puppet Skilled Framework Sandbox
Overview
Puppet Skilled is a PHP framework built on top of CodeIgniter. It adds some features to the original framework :, (*1)
This sandbox shows how to build a simple application using Puppet Skilled step by step. Each branch of this repository adds a new feature to the application as follows :, (*2)
- Step 1 : Basic architecture with a simple static page,
- Step 2 : Authentication module,
- Step 3 : Profile editing and language selection,
- Step 4 : Contact list,
- Step 5 : Users and companies backoffice.
Prerequisites
Here are some tools which can be useful to know before playing with Puppet Skilled :, (*3)
Generalities
Installation
Don't forget to create the database before the installation, then run :, (*4)
composer install
./vendor/bin/robo install
(Note: You can create an alias to be able to use robo
instead of ./vendor/bin/robo
.), (*5)
Robo will ask you to enter some informations in order to build configuration files located in .robo/build
:, (*6)
-
config.php
: Sets up the environment constant, as well as the systems
, application
and views
paths,
-
phinx.php
: Configuration file for Phinx (see Migrations in step 2),
-
application/config/config.php
: Main configuration file for the application,
-
application/config/database.php
: Database configuration,
-
application/config/email.php
: Emails configuration.
The Robo questions are defined in .robo/config/properties.php
and the answers are stored in .robo/config/my.config
., (*7)
Step 1
This step provide a basic architecture for the application with a single static page to present the main principles of Puppet Skilled., (*8)
Configuration
The application configuration is done the same way as in CodeIgniter. The configurations files are located in application/config
., (*9)
PuppetSkilled adds several files in this folder :, (*10)
-
site_settings.php
: Defines the name and title of the application, as well as the different languages available and the autoloading lang files :
$config['site_settings'] = [
'name' => 'Puppet Skilled', // Site name
'title' => 'Puppet Skilled', // Default page title
'multilingual' => [ // Multilingual settings (set empty array to disable this)
'default' => 'fr',
'available' => [
'en' => [
'label' => 'EN', // label to be displayed on language switcher
'value' => 'english', // to match with CodeIgniter folders inside application/language/
'local' => ['en_US.UTF8', 'en.UTF8'],
],
'fr' => [
'label' => 'FR',
'value' => 'french',
'local' => ['fr_FR.UTF8', 'fr.UTF8'],
]
],
'autoload' => ['general'], // Autoloads general_lang.php language file
],
];
-
template.php
: Defines the autoloaded scripts and styles for each layout available (see Views below), as well as the different folders containing the assets :
$config['default'] = $config['empty'] = $config['simple'] = [
"style_autoload" => [
'main', // Autoloads main.css
],
"script_autoload" => [
'vendor', // Autoloads vendor.js
'main' // Autoloads main.js
],
'html_style_path' => 'public/styles', // CSS path
'html_script_path' => 'public/scripts', // JS path
'html_image_path' => 'public/images', // Images path
];
Controllers
Controllers are classes extending \Globalis\PuppetSkilled\Controller\Base
and located in the application/controllers
folder. They can be used the same way as CodeIgniter controllers., (*11)
For now, only two controllers are defined :, (*12)
-
Welcome.php
: a very simple controller with a single index
method,
-
Miscellaneous.php
: a controller handling 404 errors.
Views
Concerning the view layer, Puppet Skilled is similar to CakePHP : it is organised in four parts, each one representing a folder in application/views
., (*13)
- Layout : Represents the presentational code around a view (for example header and footer). The layout used is defined in the controllers with the
$layout
attribute. In this project, you'll find three different layouts :
-
simple.php
: Extremely simple layout composed of a simple header and the page content,
-
empty.php
: Simple layout, used on pages where authentication is not needed,
-
default.php
: Complete layout with navigation displayed when the user is authentified.
- Template : Main part of the view, generally linked to a single action. In the following example, the
my_controller/action.php
template will be automatically called by the render()
method.
class MyController extends \Globalis\PuppetSkilled\Controller\Base
{
public function action()
{
$this->render();
}
}
This method accepts two optional parameters : an array containing data to be sent to the view, and the template you want to be called, which is automatically wrapped into the layout., (*14)
- Block : Blocks are just particular templates, reuseable and included in other templates. You can include a block by using
$this->block($filename)
in a view.
- Cell : A view handled by a mini controller, totally independent from any other view or any other controller. They can be used by calling
$this->cell('Controller::method')
in a view. In this project you'll find three cells :
- DebugBar : the Puppet Skilled debug bar,
- Flash : displays the error and success alerts after user actions,
- Navigation : displays the different navigation links of the application.
- Element : Little and reusable views (for example form inputs). They can be included by using
$this->element('element/path')
in your views.
When using the block
and element
methods, you can add an array as a parameter to send data to the block or element, and retrieve it in the view with $this->fetch('key')
. For example, create an input element as follows :, (*15)
$this->element('form/block_input',
[
'input_element' => 'form/input',
'label' => 'lang:general_label_search',
'name' => 'search',
]
);
In every view, the languages are externalised using CodeIgniter Language Class., (*16)
Debug bar
Puppet Skilled provides developers with a debug bar, using the CodeIgniter profiler and benchmark libraries. The profiler library is extended in application/library/APP_Profiler.php
, and the DebugBar cell is in application/views/Cell
., (*17)
The debug bar is displayed at the top of the page (it unfolds when the little red bug is clicked), and gives you access to useful pieces of information :, (*18)
- Execution time,
- Memory usage,
- GET, POST and SESSION variables,
- Configuration variables,
- HTTP headers,
- SQL requests.
You can activate it by adding $this->output->enable_profiler(true)
before calling the render
method in your controllers., (*19)
Step 2
This rather substantial step adds several functionalities to the application :, (*20)
- Services and libraries definitions,
- Database creation and population,
- A complete authentication module to the application, enabling login, logout, password setup and password reset.
The architecture was also slightly modified, a Base controller in application/core
was added to mutualise lang initialisation, access control, flash messages or the debug bar activation., (*21)
Dependency container
Puppet Skilled uses Pimple to manage the dependencies of the application. The container is located in application/hooks/PuppetSkilledBootstrap.php
and is called by the pre_controller
hook of CodeIgniter in application/config/hooks.php
:, (*22)
$hook['pre_controller'] = [
'class' => 'PuppetSkilledBootstrap',
'function' => 'boot',
'filename' => 'PuppetSkilledBootstrap.php',
'filepath' => 'hooks',
];
You can define the different services your need as follows :, (*23)
use \Globalis\PuppetSkilled\Core\Application;
class PuppetSkilledBootstrap
{
public function boot()
{
$application = Application::getInstance();
$container = $application->getContainer();
$container['serviceName'] = function () {
return new App\Service\ServiceClass();
};
}
}
The service is then accessible in the whole application via app()->serviceName
or $this->serviceName
in controllers., (*24)
Some services are available and ready to use within Puppet Skilled :, (*25)
- Authentication service :
\Globalis\PuppetSkilled\Auth\Authentication
,
- Settings service :
\Globalis\PuppetSkilled\Settings\Settings
,
- Queue service :
\Globalis\PuppetSkilled\Queue\Service
.
These services are described a few sections below., (*26)
This file also loads the Session library and the ORM., (*27)
Models and ORM
The model layer of Puppet Skilled extends Eloquent, the ORM used by Laravel. To create a model, just write a class extending Globalis\PuppetSkilled\Database\Magic\Model
., (*28)
Puppet also adds the possibility of adding some useful traits to your models :, (*29)
-
\Globalis\PuppetSkilled\Database\Magic\Uuid
: Sets the model key to be an UUID string.
-
\Globalis\PuppetSkilled\Database\Magic\SoftDeletes
: Usage of a deleted_at field instead of actually removing an entity from the database when calling $model->delete()
. Don't forget to add this field to your database structure.
-
\Globalis\PuppetSkilled\Database\Magic\Lock\Lockable
: Entity locking to prevent simultaneous interactions. Lock your entity by using $entity->acquireLock()
, which returns false if it is already locked by another user, and release the lock once your modifications are done by calling $entity->releaseLock()
. The lock is automatically destroyed after a delay defined by the lockDefaultTime
attribute of the model, or 150 seconds if not defined.
-
\Globalis\PuppetSkilled\Database\Magic\Revisionable\Revisionable
: Keeps a history of every change made to the entity in the revision table. You may want to add a $nonRevisionable
attribute to your model, containing all the fields whose modifications won't lead to a new revision (created_at, updated_at, deleted_at).
Migrations
Puppet Skilled uses Phinx to handle changes in the database structure, and Robo tasks are already configured to use it., (*30)
- Create a migration :
robo migrate:create NameInCamelCase
, the migration file is created in migrations/
- Run migrations :
robo migrate:up
- Roll back a migration :
robo migrate:down
You can also create and run seeds to insert data in your database., (*31)
- Create a seed :
robo seed:create SeedName
, the SeedName.php
file is created in seeds/
- Run a seed :
robo seed:run [SeedName]
Execute robo seed:run
to create a company and three users., (*32)
The Faker library might be useful while writing your seeds., (*33)
Authentication
The Authentication module is built on the \Globalis\PuppetSkilled\Auth\Authentication
service., (*34)
- Permissions : Permissions are defined by a tree. If a user has an access to a higher level than the permission currently checked, the access is granted. For example, if a user has the backoffice permission, he also has any backoffice.* permission.
- Resources : A resource makes users able to access to an entity. Extends
\Globalis\PuppetSkilled\Auth\Resource
.
- Roles : A role is a set of permissions and resources. If a role isn't linked to any resource, we consider it to have access to every resource.
In our case :, (*35)
Role |
frontoffice |
backoffice |
backoffice.user |
backoffice.companies |
Resource |
user |
1 |
0 |
0 |
0 |
App\Model\Company |
manager |
1 |
0 |
1 |
0 |
App\Model\Company |
administrator |
1 |
1 |
0 |
0 |
NULL |
Three users and a company are defined in the project seeds :, (*36)
- Michel User : user linked to the company Globalis Media Systems,
- Michel Manager : manager linked to the company Globalis Media Systems,
- Michel Administrator : administrator.
You can handle the access to controllers and methods by using the $guards
attribute of the controllers. This array is used by the secureAccess
method of the Base controller. For example :, (*37)
protected $guards = [
'index' => 'backoffice.user.view',
'add' => 'backoffice.user.edit',
'edit' => 'backoffice.user.edit',
'delete' => 'backoffice.user.edit',
];
This way, users who have the backoffice.user permission will have access to every method, and users who have backoffice.user.view will only have access to index
., (*38)
This service also provides some useful methods :, (*39)
-
app()->authenticationService->isLoggedIn()
: Checks if the user is authentified.
-
app()->authenticationService->user()
: Returns the user currently authentified.
-
app()->authenticationService->userCan($permission, $resourceType, $resourceValue)
: Checks if the authentified user has the permission to access a certain resource (or has the global permission if the resources parameters are null).
- Login, logout, password setup and reset methods : see
vendor/globalis/puppet-skilled-framework/src/Auth/Authentication.php
for more details.
Sessions are handled by a custom session driver, which extends the CodeIgniter driver. This driver stores the sessions into the sessions
table of the database. It is configured by the sess_*
parameters in the config/config.php
file., (*40)
Settings
A settings service is avaible in \Globalis\PuppetSkilled\Settings\Settings
, which enables you to retrieve global parameters for the application in the database. Settings are defined as a simple key-value structure, with an extra autoload
field., (*41)
This service uses the settings
table, and its usage is quite simple :, (*42)
-
app()->settings->get($key)
: Retrieve a setting from the database.
-
app()->settings->update($key, $value)
: Change the value of a setting to $value
.
Queue
Tasks like sending mails can be handled by a job queue. A queue service is available in \Globalis\PuppetSkilled\Queue\Service
. Jobs are defined in classes extending \Globalis\PuppetSkilled\Queue\Queueable
.
The job is put into the queue as follows :, (*43)
app()->queueService->dispatch($job);
To execute the jobs in the queue, execute the following command at the root of the project :, (*44)
php index.php queue
A queueable App\Jobs\Mailer
class is available in app/jobs
and uses the CodeIgniter Email class., (*45)
Step 3
This step adds a simple form enabling the user to edit his personal informations, using the CodeIgniter Form Validation Class., (*46)
Custom services
This step provides you with a Language service to enable the user to switch between English and French languages. You can create your own services by extending the Puppet Skilled Service class as follows :, (*47)
namespace App\Service;
class Language extends \Globalis\PuppetSkilled\Service\Base
{
}
Just inject this service in the application in application/hooks/PuppetSkilledBootstrap.php
:, (*48)
$container['languageService'] = function ($c) {
return new \App\Service\Language();
};
Now you can use it in your application like any other services by calling app()->languageService
., (*49)
Helpers
The framework defines some useful functions in its core : see the file /vendor/globalis/puppet-skilled-framework/src/Core/helpers.php
. It contains functions to help developers handling arrays and strings, or the app()
function we have used before., (*50)
Moreover, in the application/helpers
folder you'll find some files "extending" several CodeIgniter native helpers. We've extended the date, form, language and url helpers to improve some functions and add some others., (*51)
-
APP_date_helper.php
is using the great Carbon library, which extends the PHP DateTime class, in useful functions formating dates.
-
APP_form_helper.php
replaces form_open
and adds functions to handle required fields.
-
APP_language_helper.php
adds the lang_libelle
function which wraps the original lang
function.
-
APP_url_helper.php
creates functions to help working with URLs (creating links, redirecting, etc).
Open these files to see the functions they provide and their precise usage. You can also define your own helpers - see the CodeIgniter documentation., (*52)
Step 4
This step adds a contact list module which lists the users of the platform. It uses two Puppet Skilled libraries :, (*53)
QueryFilter
This library is located in \Globalis\PuppetSkilled\Library\QueryFilter
and enable the developer to quickly create user filters on a SELECT request, to display users who belong to a certain company for instance., (*54)
$filters = new QueryFilter(
[
'filters' => [ // Filter definitions using closures
'last_name' => function($query, $value) {
return $query
->where('last_name', 'like', $value . '%');
},
],
'save' => 'frontoffice_contact_filters', // Key for session storage or false
'method' => 'get', // Method used by the form
'action' => 'action', // Key of the POST/GET variable
'filter_action' => 'filter', // Value of the variable to apply the filters
'reset_action' => 'reset', // Value of the variable to clear the filters
'default_filters' => [ // Default value for the filters
'last_name' => 'Default value',
],
]
);
Use $filters->run(Model::query())
to run the filters and send $filters
to your view, in which you create a corresponding form with an input field named last_name
and using these methods of the QueryFilter object :, (*55)
-
getActionName()
: key of the POST/GET variable sent by the form,
-
getResetActionValue()
: name of the submit button,
-
getFilterActionValue()
: name of the reset button.
This library is located in \Globalis\PuppetSkilled\Library\QueryPager
and enable the developer to quickly set up pagination and sorting on a SELECT request., (*56)
$pager = new QueryPager(
[
'limit_choices' => [10, 20, 50], // Items per page choices
'limit' => 10, // Items per page by default
'sort' => [ // Sortable columns
'last_name' => 'last_name',
'email' => 'email',
],
'save' => 'frontoffice_contact_pager', // Key for session storage
'unique_order_key' => Model::query() // Unique key for ordering or false
->getModel()
->getKeyName(),
'page' => 1, // Default page number
'request_param' => [ // Available parameters to organise the results
'page' => 'page',
'order' => 'order',
'direction' => 'direction',
'limit' => 'pagesize'
],
]
);
Use $pager->run(Model::query())
to run the pager and send it to your view, in which you can use these methods :, (*57)
-
getResult()
: Returns an array with the following keys :
[
'result', // Result of the SQL query
'page', // Current page
'total', // Total number of elements
'perPage', // Elements per page
'prevPage', // True if a previous page exists
'nextPage', // True if a next page exists
'pageCount', // Number of pages
'sort', // Columned used to order the results
'direction', // Direction of the ordering (asc/desc)
'limit', // Limit
'limit_choices' // Available limit choices
]
-
isSortable($fieldName)
: Checks if the column $fieldName
is sortable.
In order to sort the results, use two GET parameters : order
for the field name and direction
for an ascending or descending sort (asc
or desc
)., (*58)
CRUD elements are already available in order to use your filters and pager, or you can write your own.
See precise usage in application/controllers/frontoffice/Contact.php
, the corresponding views in application/views/frontoffice/contact/
and the CRUD elements in application/views/Element/crud/
., (*59)
Step 5
This step adds a backoffice module to the application in order to edit companies and users. Two pages, backoffice/company
and backoffice/user
enable users to edit, add and delete companies and users., (*60)
Administrators have access to both of them whereas managers can only access the latest. Simple users don't have access to the backoffice at all., (*61)
Moreover, managers only have the right to edit and add users belonging to the companies they belong to themselves., (*62)
To simplify access control, the Authentication service has been extended so we could add a method checking whether a user can edit another user entity., (*63)