tigron / skeleton-core
Core functionality for Skeleton
Installs: 15 626
Dependents: 11
Suggesters: 0
Security: 0
Stars: 0
Watchers: 6
Forks: 0
Open Issues: 0
Suggests
- dev-master
- 4.x-dev
- v4.0.6
- v4.0.5
- v4.0.4
- v4.0.3
- v4.0.2
- v4.0.1
- v4.0.0
- 3.x-dev
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- 1.x-dev
- v1.4.9
- v1.4.8
- v1.4.7
- v1.4.6
- v1.4.5
- v1.4.4
- v1.4.3
- v1.4.2
- v1.4.1
- v1.4.0
- v1.3.5
- v1.3.4
- v1.3.3
- v1.3.2
- v1.3.1
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v0.1.17
- v0.1.16
- v0.1.15
- v0.1.14
- v0.1.13
- v0.1.12
- 0.1.11
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- 0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- v0.0.5
- v0.0.4
- v0.0.3
- v0.0.2
- v0.0.1
This package is auto-updated.
Last update: 2024-03-20 14:49:58 UTC
README
Description
This library contains the core functionality of skeleton
. It handles the
bootstrapping and, when used in a web context, it will receive and handle the
HTTP request.
The package will automatically detect "applications", which are separate parts
of your project. Each application can implement its own user interface.
Applications are detected based on the URL requested by the user. Usually based
on the hostname, skeleton-core
will search for the matching application.
For example, one could create one application for a public website while a
second application can handle the administrative interface. The public
application would for example listen on www.example.com
, while the
administrative interface would listen for requests to admin.example.com
.
The request will then be finalized by the matching module in the application. By
default, the module is matched by mapping the URI on the local filesystem path,
but this behaviour can be altered using routes
.
One special case, is handling of so-called media
files. When a media file is
requested, detection of which happens based on the requested file's extension,
it will be served from the media/image
, media/javascript
or media/css
in
the matching application directory.
Installation
Installation via composer:
composer require tigron/skeleton-core
After installation you can start a skeleton project.
Getting ready
Skeleton core offers a Config object that is populated from a given config directory. The Config object automatically includes all php files which are stored in the config directory. Each php file should return a php array. Each key/value pair will be available in your project.
Include a config directory
\Skeleton\Core\Config::include_path('/config');
PHP files stored in the config directory will be evaluated in alphabetical
order. In case you have environment-specific configuration, you can create a
file environment.php
in your config directory which will be evaluated last.
Get a config object
$config = \Skeleton\Core\Config::get();
Skeleton needs at least these config items to operate properly:
'application_path': The root path where skeleton can find Skeleton
Applications
Your webserver should rewrite every request to a single PHP file. This file will start your skeleton project. It should include at least the following
\Skeleton\Core\Config::include_path('config');
\Skeleton\Core\Web\Handler::Run();
Altough a skeleton project can have any desired directory structure, we encourage to use the following:
- app
- config
- lib
- model
- external
- package
- asset
If more skeleton packages are installed, this structure can easily be extended to support additional features (file storage/translations/migrations/...)
Applications
The application dir will automatically be scanned for Skeleton Applications. Each subdirectory will be seen as a fully independent \Skeleton\Core\Application
There are various types of applications. Skeleton-core includes the most-used:
\Skeleton\Core\Application\Web
Other applications are available via skeleton packages (eg skeleton-application-api
A Skeleton Application\Web is a common application that handles any type of web interface. It has modules/templates/events and can contain its own media. For an Application\Web to work properly, it is important to respect the correct directory structure within the application:
- app
- your application's folder
- config
- application_config1.php
- application_config2.php
- event
- module
- template
- media
- css
- javascript
- image
If you want media files to be served from additional paths, for example
if you have a package manager such as bower
, yarn
or even the
fxp/composer-asset-plugin
, you can specify additional paths which will
be searched in addition to the default media
one.
To enable media serving from an additional asset path, include the following configuration directive in your skeleton project:
'asset_paths': The asset paths where media files can be served from if
they are not found in your application.
Application configuration
Applications can have their own configuration. The application's configuration
is done via configuration files placed in the config
directory of your app.
Similar as the global project configuration, every PHP file will be evaluated
in alphabetical order and should return an array with configuration directives.
If you have environment-specific configurations, they can be included in
environment.php
which will be evaluated last.
The following optional configurations can be set:
Configuration | Description | Default value | Example values |
---|---|---|---|
hostnames | (required)an array containing the hostnames to listen for. Wildcards can be used via * . |
[] | [ 'www.example.be, '*.example.be' ] |
base_uri | Specifies the base uri for the application. If a base_uri is specified, it will be included in the reverse url generation | '/' | '/v1' |
default_language | This is the ISO 639-1 code for the default language to be used, if no more specific one can be found | 'en' | 'en', 'nl', any language iso2 code provided by skeleton-i18n |
session_name | The name given to your session | 'App' | any string |
sticky_session_name | The key in your session where sticky session information is stored | 'sys_sticky_session' | any string |
csrf_enabled | Enable CSRF | false | true/false |
replay_enabled | Prevent replay attack | false | true/false |
hostnames | Array with hostnames that should be handled by the application | [] | array with any hostname(s) |
routes | Array with route information | [] | See routes |
module_default | The default module to search for | 'index' | |
module_404 | The 404 module on fallback when no module is found | '404' | |
sticky_pager | Enable sticky pager | false | Only available if skeleton-pager is installed |
route_resolver | Closure to provide module resolving based on requested path | Internal module resolver |
Application namespaces
All PHP classes in your Skeleton application should be whitin your Application Namespace, which is:
\App\APP_NAME\
For modules, the specific namespace is
\App\APP_NAME\Module
For events, the specific namespace is
\App\APP_NAME\Event
routes
An array which maps routes
to modules
. A route definition cab be used to
generate pretty URL's, or even translated versions. Usage is best described by
an example.
[
'web_module_index' => [
'$language/default/route/to/index',
'$language/default/route/to/index/$action',
'$language/default/route/to/index/$action/$id',
'$language[en]/test/routing/engine',
'$language[en]/test/routing/engine/$action',
'$language[en]/test/routing/engine/$action/$id',
],
],
Usage
Routing to the correct application
Based on the Host
-header in the request, the correct application will be
started. This is where the hostnames
array in the application's configuration
file (shown above) will come into play.
If skeleton-core
could find a matching application based on the Host
-header
supplied in the request, this is the application that will be started.
If your application has base_uri
configured, that will be taken into account
as well. For example: the application for a CMS can be distinguished by setting
its base_uri
to /admin
.
Routing to the correct module
Requests that do not have a file extension and thus do not match a media
file,
will be routed to a module and a matching method. The module is determined based
on the request URI, excluding all $_GET parameters. The module is a class that
should be derived from \Skeleton\Core\Application\Web\Module
.
This can be best explained with some examples:
requested uri | classname | filename |
---|---|---|
/user/management | \App\APP_NAME\Module\User\Management | /user/management.php |
/ | \App\APP_NAME\Module\Index | /index.php |
/user | \App\APP_NAME\Module\User | /user.php |
/user | \App\APP_NAME\Module\User\Index | /user/index.php |
As you can see in the last two examples, the index
modules are a bit special,
in that they can be used instead of the underlying one if they sit in a
subfolder. The index
is configurable via configuration directive
module_default
Routing to the correct method
A module can contain multiple methods that can handle the request. Each of those requests have a method-name starting with 'display'. The method is defined based on the $_GET['action'] variable.
Some examples:
requested uri | classname | method |
---|---|---|
/user | \App\APP_NAME\Module\User | display() |
/user?action=test | \App\APP_NAME\Module\User | display_test() |
Bootstrapping a specific module
You can add a 'bootstrap()' method to your module to execute this part of code everytime the module is accessed.
Securing a specific module
You can add a 'secure(): bool' method to your module to determine if the module can be accessed with the given data.
Handling of media files
If the requested url contains an extension which matches a known media type, the
requested file will be served from the media/
directory of the application.
If the requested media file could not be found, skeleton-core
will search for
a matching file in the folder specified by configuration directive asset_dir
(if any).
CSRF
The skeleton-core
package can take care of automatically injecting and
validating CSRF tokens for every POST
request it receives. Various events have
been defined, with which you can control the CSRF flow. A list of these events
can be found further down.
CSRF is disabled globally by default. If you would like to enable it, simply
flip the csrf_enabled
flag to true, via configuration directive csrf_enabled
Once enabled, it is enabled for all your applications. If you want to disable it
for specific applications only, flip the csrf_enabled
flag to false
in the
application's configuration.
Several events are available to control the CSRF behaviour, these have been documented below.
When enabled, hidden form elements with the correct token as a value will
automatically be injected into every <form>...</form>
block found. This allows
for it to work without needing to change your code.
If you need access to the token value and names, you can access them from the
env
variable which is automatically assigned to your template. The available
variables are listed below:
- env.csrf_header_token_name
- env.csrf_post_token_name
- env.csrf_session_token_name
- env.csrf_token
One caveat are XMLHttpRequest
calls (or AJAX
). If your application is using
jQuery
, you can use the example below to automatically inject a header for
every relevant XMLHttpRequest
.
First, make the token value and names available to your view. A good place to do
so, might be the document's <head>...</head>
block.
<!-- CSRF token values -->
<meta name="csrf-header-token-name" content="{{ env.csrf_header_token_name }}">
<meta name="csrf-token" content="{{ env.csrf_token }}">
Next, we can make use of jQuery
's $.ajaxSend()
. This allows you to
configure settings which will be applied for every subsequent $.ajax()
call
(or derivatives thereof, such as $.post()
).
$(document).ajaxSend(function(e, xhr, settings) {
if (!(/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) && !this.crossDomain) {
xhr.setRequestHeader($('meta[name="csrf-header-token-name"]').attr('content'), $('meta[name="csrf-token"]').attr('content'));
}
});
Notice the check for the request type and cross domain requests. This avoids sending your token along with requests which don't need it.
Replay
The built-in replay detection tries to work around duplicate form submissions by users double-clicking the submit button. Often, this is not caught in the UI.
Replay detection is disabled by default, if you would like to enable it, flip
the replay_enabled
configuration directive to true.
You can disable replay detection for individual applications by setting the
replay_enabled
flag to false
in their respective configuration.
When the replay detection is enabled, it will inject a hidden __replay-token
element into every form
element it can find. Each token will be unique. Once
submited, the token is added to a list of tokens seen before. If the same token
appears again within 30 seconds, the replay detection will be triggered.
If your application has defined a replay_detected
event, this will be called.
It is up to the application to decide what action to take. One suggestion is to
redirect the user to the value HTTP referrer, if present.
Events
Events can be created to perform a task at specific key points during the application's execution.
Events are defined in Event
context classes. These classes are optional, but
when they are used, they should be located in the event
directory of your
application. The filename should be in the form of Context_name.php
, for
example Application.php
.
The class should extend from Skeleton\Core\Event
and the classname should be
within the namespace \App\APP_NAME\Event\Context
, where
APP_NAME
is the name of your application, and Context
is one of the
available contexts:
- Application
- Media
- Module
Example of a Module
event class for an application named admin
:
<?php
/**
* Module events for the "admin" application
*/
namespace App\Admin\Event;
class Module extends \Skeleton\Core\Event {
/**
* Access denied
*
* @access public
*/
public function access_denied() {
\Skeleton\Core\Web\Session::redirect('/reset');
}
}
The different contexts and their events are described below.
Application context
bootstrap
The bootstrap method is called before loading the application module.
public function bootstrap(\Skeleton\Core\Web\Module $module) { }
teardown
The teardown method is called after the application's run is over.
public function teardown(\Skeleton\Core\Web\Module $module) { }
detect
The detect method is called on every request to determine if the application should handle the request, or if it should be skipped based on, for example, the requested hostname and the request's URI.
This event should return true
in order to proceed with this application.
public function detect($hostname, $request_uri): bool { }
Module context
access_denied
The access_denied
method is called whenever a module is requested which can
not be accessed by the user. The optional secure()
method in the module
indicates whether the user is granted access or not.
public function access_denied(\Skeleton\Core\Web\Module $module) { }
not_found
The not_found
method is called whenever a module is requested which does not
exist.
public function not_found() { }
Media context
not_found
The not_found
method is called whenever a media file is requested which could
not be found.
public function not_found() { }
Error context
The error event context is not actually part of skeleton-core
, but rather of
skeleton-error
.
exception
The exception
method is called whenever an exception has not been caught.
public function exception() { }
Security context
csrf_validate_enabled
The csrf_validate_enabled
method overrides the complete execution of the
validation, which useful to exclude specific paths. An example implementation
can be found below.
public function csrf_validate_enabled(): bool {
$excluded_paths = [
'/no/csrf/*',
];
foreach ($excluded_paths as $excluded_path) {
if (fnmatch ($excluded_path, $_SERVER['REQUEST_URI']) === true) {
return false;
}
}
return true;
}
csrf_validate_success
The csrf_validate_success
method allows you to override the check result after
a successful validation. It expects a boolean as a return value.
csrf_validation_failed
The csrf_validation_failed
method allows you to override the check result
after a failed validation. It expects a boolean as a return value.
csrf_generate_session_token
The csrf_generate_session_token
method allows you to override the generation
of the session token, and generate a custom value instead. It expects a string
as a return value.
csrf_inject
The csrf_inject
method allows you to override the automatic injection of the
hidden CSRF token elements in the HTML forms of the rendered template. It
expects a string as a return value, containing the rendered HTML to be sent back
to the client.
csrf_validate
The csrf_validate
method allows you to override the validation process of the
CSRF token. It expects a boolean as a return value.
replay_detected
The replay_detected
method allows you to catch replay detection events. For
example, you could redirect the user to the value of the HTTP referrer header
if it is present:
public function replay_detected() {
if (!empty($_SERVER['HTTP_REFERER'])) {
Session::redirect($_SERVER['HTTP_REFERER'], false);
} else {
Session::redirect('/');
}
}
session_cookie
The session_cookie
method allows you to set session cookie parameters before
the session is started. Typically, this would be used to SameSite cookie
attribute.