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
app/routes.php
- Dependency Injection via "php-di/php-di" - see
app/config/config_*.php
- View (template) engine via "twig/twig"
- Sign Up / Log in functionality via "psecio/gatekeeper" (see
.env
for MySQL credentials andphinx.yml
for migration info) - currently hard-coupled into app - Password reset email via Mailgun and Guzzle (see
.env
for where to put Mailgun key and domain and seeViews/emails/forgotpass.twig
for email template) - Flash messages via "tamtamchik/simple-flash", see master layout for where they're displayed, and
config.php
for where they are passed into Twig if they exist. Inconfig.php
you 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
UsersController
which 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/crons
when logged in) - [Optional] Basic database access via CakeORM - default connection defined in
app/config/connections/default.php
.
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
Then, open 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:
vendor/bin/setup.sh
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.
This is a skeleton project, and as such it is made to be tweaked and extended.
Add more routes in app/routes.php
.
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/Templates/
or 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 routes.php
.
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
/data/db/cron.sql
-
point your system's crontab to
app/cron.php
with 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
src/Standard/Cron/ExampleCronTask.php
for example) -
Add new crons to the database via the CRUD interface by visiting
admin/crons
Note that the cron classes you write will have access to all the dependencies defined in the app/config/config_cli.php
file.
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 app/config/connections/default.php
file.
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.
See FRONTEND.md.
See I18N.md.
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 src/Standard/Views
.
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 AuthController::forgotPassword
.
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.
To switch debug mode on/off, change it in .env
. See .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 .env
.
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 src
, e.g. 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:
composer dump-autoload
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 app/config/shared/root.php
under viewsFolders
:
// ...
'viewsFolders' => [__DIR__.'/../../../src/Standard/Views', __DIR__.'/../../../src/MyApp/Views']
// ...