SitePoint Skeleton Application
A lightweight (relatively, compared to modern frameworks) skeleton app with login / registration functionality. Useful for framework-agnostic tutorials.
The biggest difference when compared to typical frameworks is full support for a front-end build toolchain entirely without NodeJS, so you can be sure your builds will always work, and work well. See below for more info.
It includes the following out of the box (detailed descriptions of features are lower in this document):
- Routing via "nikic/fast-route" - see
- Dependency Injection via "php-di/php-di" - see
- View (template) engine via "twig/twig"
- Sign Up / Log in functionality via "psecio/gatekeeper" (see
.envfor MySQL credentials and
phinx.ymlfor migration info) - currently hard-coupled into app
- Password reset email via Mailgun and Guzzle (see
.envfor where to put Mailgun key and domain and see
Views/emails/forgotpass.twigfor email template)
- Flash messages via "tamtamchik/simple-flash", see master layout for where they're displayed, and
config.phpfor where they are passed into Twig if they exist. In
config.phpyou can also define a custom pre-made style to the templates - many popular CSS frameworks are supported. Defaults to SemanticUI.
- Annotation-based ACL (for controlling access to classes and methods, not routes) via SitePoint/Rauth
- User management: as a demonstration of controllers and some basic CRUD operations, see the
UsersControllerwhich provides user and group CRUD.
- Error logging via Monolog and/or Bugsnag
- [Optional] OOTB gettext support for multi-language apps. See here.
- [Optional] Automatic image resizing for media queries via league/glide - see below for explanation
- [Optional] Validation with "respect/validation" (usage example in AuthController - currently hard-coupled to app)
- [Optional] Cronjobs via Jobby and built-in cronjob CRUD (see
/admin/cronswhen logged in)
- [Optional] Basic database access via CakeORM - default connection defined in
Additionally, the project includes support for an optional front-end workflow without NodeJS and NPM: full build chains and file watchers included. For more information about this approach, see here.
- MySQL (due to Gatekeeper)
- PHP 7+ (due to sanity)
- a decent development environment. Homestead Improved will do just fine.
git clone https://github.com/swader/nofw myproject cd myproject composer install
data/db/gatekeeper_init.sql and change the username, password, and database name if needed. Then run the SQL script by either pasting it into a MySQL management tool, or directly from the command line:
mysql -u myuser -p < data/db/gatekeeper_init.sql
Finally, finish Gatekeeper installation by running:
Enter all the required data, and make sure it matches the data just entered via the SQL script.
That's it. Point the server at
public/index.php and enjoy.
To create the first user, who will automatically be given the "admin" group, follow the instructions and just register a regular account. Every subsequent user will be given the regular "users" group.
To limit access to certain classes or methods, see SitePoint/Rauth for documentation, and AccountController for example usage.
Features and Fine Tuning
This is a skeleton project, and as such it is made to be tweaked and extended.
Add more routes in
Unlike traditional access control lists, this project uses SitePoint/Rauth which lets you limit access to specific classes and/or methods via annotations on those classes and/or methods. The process is automatic with controllers - it's configured in
index.php before the dispatcher calls the requested class::method pair. For manual control of all other classes, just grab the Rauth instance out of the container with
$container->get('rauth') and then call
authorize on that instance, as per Rauth docs.
See the GK docs to learn about it. Basically, the logged in user is always accessible via the container, by calling
$container->get('User'). If there is no logged in user,
null is returned.
Even though Gatekeeper supports group and permission hierarchy, it is recommended they be used as basic flags. So if someone is an "admin", it is not to be assumed they are also a "moderator" - they should have both the "moderators" and "admin" group in order to see all the actions available to both groups. When the first user account is created, two groups are also automatically created if they don't yet exist: "admin" and "users".
All services are built in
app/config/config_*.php as part of the PHP-DI dependency injection container. Whenever you feel like calling the
new keyword in a controller or another service, reconsider and do it there instead. This has several advantages, not the least of which are the fact that all services are registered in one place and easily tracked, and the fact that your application is not tightly coupled to those services - you can replace their implementation with something else in the configuration later on, and as long as the API is the same, the app needn't know about the change.
There are two config files - one aimed at the web version of the app, and one aimed at the CLI version. While the app doesn't have a CLI endpoint as such, it does have a cron endpoint (
app/cron.php) which utilizes this CLI configuration. Study the respective files for more information.
The application comes with Twig out of the box. Several global variables are predefined for the template engine, for easy access in the templates. To see which ones, please inspect the relevant section in
app/config/config_web.php. To add more folders in which to look for templates to Twig, please see
app/config/shared/root.php in which site-wide configuration is set up.
To get access to flash messages, either retrieve the flasher instance from the container (
$container->get(Tamtamchik\SimpleFlash\Flash::class)) or have it auto-injected into controllers (see
AuthController for example and PHP-DI docs for documentation about this). Autoinjection is already set up if you use the abstract standard Controller included with the app.
To style the messages, several pre-configured templates exist that you can inject into the Flash class. Most popular CSS frameworks are covered. For available templates, see
vendor/tamtamchik/simple-flash/src/TemplateFactory.php. Change the injected template in
app/config/config_web_.php (notice that SemanticUI is injected by default).
This skeleton comes with Glide which generates resized images from a source image on-demand - perfect for media queries. It also saves them for later, so the next time they're requested, they don't need to be regenerated. In a nutshell, this allows you to have a single image like
assets/image/xyz.png, and then request it with
/static/image/xyz-WIDTH.png and it will get automatically generated at that width.
For a demonstration of this, see the homepage when you install the project, or read this tutorial.
Note that while this is on by default, it is entirely optional - you can disable this image generation by commenting out the related route in
This skeleton also comes with Jobby for optional cronjob management via PHP.
To activate this part:
add tables needed for crons to work to your database. Find the SQL to import in
point your system's crontab to
app/cron.phpwith a line like:
* * * * * cd /home/vagrant/Code/nofw/app && php cron.php 1>> /dev/null 2>&1
This will make sure the cron endpoint is called every minute.
Write new cron tasks (see
Add new crons to the database via the CRUD interface by visiting
Note that the cron classes you write will have access to all the dependencies defined in the
CakeORM was used for super-easy access to cron tables (see Cronjobs section above), but can be reused for the rest of the app, too. The default connection is defined in the
Tables are accessed like this:
$crons = TableRegistry::get('Cron')->find()-> ... ;
For more information, and to learn how to use CakeORM, please see their docs. Do keep in mind that if you don't like CakeORM or want to use your own database access layer, you're entirely free to do so.
When a route is not found, a 404 page is automatically rendered. When the wrong verb is used on a route (like
GET instead of
POST on a
processSignup method), a 500 error is rendered. Both of these templates are in
Sending "forgot password" emails is done via Mailgun, purely because it's simplest. The email templates are in
src/Standard/Views/emails. However, it should be noted that it's currently used without a Mailgun client package or any other dependency, so really, the emailing system is completely optional and interchangeable. If, however, you do choose to use Mailgun, put the credentials into the
.env file (see
.env.example for inspiration) and then see how it was done in
Error logging with Monolog and/or Bugsnag
Monolog is installed by default and registered as the default error handler. This means that all errors will be logged to
logs/error.log. Change the log file or the log level in the respective
config_*.php. Bugsnag is installed, too, for those who want to use it. Just change the key in
.env and register the Bugsnag handler.
Debug mode and environment
To switch debug mode on/off, change it in
.env.example for inspiration. Currently debug mode only affects the output of permissions errors via Rauth (see
index.php). You can also change the application environment by modifying the
APPLICATION_ENV key in
When extending the application with your own services and controllers, it is recommended you use a different namespace than the
Standard included by default.
Standard is there for the basic skeleton stuff, and will be improved further with time.
To write your own controllers, classes, etc, make a new folder in
MyNamespace, and then put your stuff in there. Don't forget to register this new namespace in
composer.json and regenerate autoload files by running:
To add views for this new namespace, add a
Views folder in there, like in
Standard, and then include this new view folder in the configuration, i.e. add an entry to
// ... 'viewsFolders' => [__DIR__.'/../../../src/Standard/Views', __DIR__.'/../../../src/MyApp/Views'] // ...