Cohesion PHP Framework¶
The primary objective of this framework is to provide a simple framework to help developers create good clean PHP code. It forces developers to write code that follows many common software development principles.
Cohesion is more than just a Framework, it’s a development guideline that tries to enforce best practices to improve maintainability.
Contents¶
Introduction¶
Origins¶
Cohesion started from me deciding to start a new project in PHP. I didn’t have much experience with many PHP frameworks so rather than looking through all the existing frameworks and picking the one that seemed to make the most sense to me, I decided to list all the ‘features’ I wanted in a framework before trying to find one suitable.
Coming from a long history of programming in a myriad of languages, I realised that one of my main requirements was to have a strong code structure to provide a strong backbone of the application so that it’s very easy to maintain in the future. I have managed large teams of software engineers maintaining large code bases and I know how painful it can be to add new features or change existing ones when every engineer decides to implement his own structure.
In going through all the existing frameworks I found that none of them really gave me what I wanted. Probably the closest ones would be Symfony and Laravel but still they didn’t really give me everything I needed. So I set out to develop a simple framework that will give me everything I need and will help make sure that I will be able to continue to easily add more features in the future even when my project has grown to hundreds of classes and will be able to easily go in and optimize the data access etc without having to worry about effecting any business logic etc.
One of the issues I have with many of the other frameworks is that they provide a good foundation for developing “anything” but then let the developers do whatever they want on top of it. They pride themselves as being extremely customizable and letting users choose how they want to use it. While that’s fantastic for many people, I want something that will provide more than a foundation, I don’t want other people to “use it how ever they want”. In going with this theme I’ve tightened “how” Cohesion can be used down a bit and don’t have as many “options”.
Philosophy¶
Cohesion is for people who envision their product continuing to grow and place a high value on maintainability and low technical debt.
Cohesion tries to bring many of the well established development “best practices” into the main development pipeline for PHP projects.
I’m sure Cohesion won’t be for everyone, as most people are happy with something they can quickly hack a website on top of and continue just quickly hacking stuff on top.
It provides the framework for you to create a service based architecture for your applications.
Principles¶
- High Cohesion
- High Cohesion is when objects are appropriately focused, manageable and understandable. It basically means that everything within the same component should be strongly related to each other.
- Low Coupling
- A loosely coupled system is one in which each of its components has, or makes use of, little or no knowledge of the definitions of other separate components. Not relying on the implementation of other components means that if any individual component should change then it shouldn’t effect any other component.
- Single Responsibility
- The single responsibility principle states that every context (class, function, variable, etc.) should have a single responsibility, and that responsibility should be entirely encapsulated by the context. All its services should be narrowly aligned with that responsibility.
- Dependency Inversion
- Components should “use” abstractions of other libraries / utilities / modules. Then the concrete implementation can be passed to the object as run time. This allows for the interchanging of implementations without having to deal with modifying other components that “use” the class.
- Controller
- The Controller is a component often referenced as a part of MVC frameworks. It’s role is to separate the Business Logic and the Presentation Logic.
- Creator
- The factory pattern allows for a specific class that is responsible for creating new instances of objects. This allows for the code to delegate creating of instances meaning that each module doesn’t need to know “how” to create an instance of that object.
Installation¶
This documentation currently focuses on installing Cohesion on a Debian/Ubuntu system.
Requirements¶
Once PHP is installed and set up adding additional libraries will be handled by Composer but there are still a few things you need to install yourself before then.
- PHP version 5.4 or above
- PHP-FPM
- MySQLnd extension
- APCu extension
- CURL extension
Installing PHP and required extensions¶
sudo apt-get install php5 mysql-client php5-mysqlnd php5-fpm php5-curl php-pear php5-dev
Install APCu for fast access caching:
sudo pecl install apcu
You’ll most likely have to add the extension to your php.ini:
sudo echo "extension=apcu.so" > /etc/php5/mods-available/apcu.ini
sudo ln -s /etc/php5/mods-available/apcu.ini /etc/php5/conf.d/20-apcu.ini
sudo ln -s /etc/php5/mods-available/apcu.ini /etc/php5/fpm/conf.d/20-apcu.ini
The paths may be different depending on your distribution
Make PHP-FPM use a Unix socket¶
By default PHP-FPM is listening on port 9000 on 127.0.0.1 in some distributions. You should make PHP-FPM use a Unix socket which avoids the TCP overhead. To do this, open /etc/php5/fpm/pool.d/www.conf
:
sudo vi /etc/php5/fpm/pool.d/www.conf
Find the existing listen
line and comment it out and add the new one as shown here:
[...]
;listen = 127.0.0.1:9000
listen = /var/run/php5-fpm.sock
[...]
Installing Composer¶
Composer is the package manager used by modern PHP applications and is the only recommended way to install Cohesion. To install composer run these commands:
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
Installing Nginx¶
It’s recommended to use Nginx with Cohesion. Nginx is a very lightweight web server and is much more efficient than Apache and very easy to configure:
sudo apt-get install nginx
Installing Cohesion with Composer¶
Once composer is installed run the following command to create a new project in a myproject
directory using Cohesion:
composer create-project cohesion/cohesion-framework -sdev myproject
Composer will then download all required dependencies and create the project directory structure for you.
After composer finishes the installation process the installer will ask you Do you want to remove the existing VCS (.git, .svn..) history? [Y,n]?
just hit <Enter>
to safely remove the Cohesion git history. This will prevent you from polluting your projects version history with Cohesion commits. It will also make it easier to set up your own version control for your project.
Setting up Nginx server¶
Default Nginx configurations are provided within the /config/nginx/
directory of your project. So that we can keep our server configurations in version control we’ll just link to the configuration file you want to use for the current environment within our project:
sudo ln -s /full/path/to/myproject/conf/nginx/local.conf /etc/nginx/sites-available/myproject-local
sudo ln -s /etc/nginx/sites-available/myproject-local /etc/nginx/sites-enabled/myproject
Note
Your nginx directory might be somewhere else depending on your distribution.
Open up the configuration and set the root
path to the www
directory within your project:
vi config/nginx/local.conf
You can create additional Nginx configuration files for your different environments just remember to change the server_name
, root
path and APPLICATION_ENV
.
Restart nginx:
sudo service nginx restart
Now you should be able to see a default Cohesion welcome page when you go to http://localhost.
Quickstart¶
After installing a clean Cohesion project you should modify your composer.json file and set the project name etc for your project.
You main application code will go into the src/
directory. You should create separate subdirectories within that folder for each of your services.
All front end code goes within the www/
directory. With your controllers in www/controllers
, views in www/veiws
and templates in www/templates
.
Frontend¶
If you haven’t changed your configuration yet your default controller should be ‘Home’ with the default function on ‘index’. This means that if you go to http://localhost/ it should look for the HomeController
and execute the index
function on it.
The Cohesion Framework comes with a very simple home page. Open up www/controllers/HomeController.php
you should see it looks like this:
use \Cohesion\Structure\Controller;
use \Cohesion\Structure\Factory\ViewFactory;
class HomeController extends Controller {
public function index() {
$view = ViewFactory::createView('Home');
return $view->generateView();
}
}
Here we can see it’s creating a ‘Home’ view. This can be found at www/views/HomeView.php
.
class HomeView extends MyView {
public function __construct($template, $engine, $vars) {
parent::__construct($template, $engine, $vars);
$vars['page'] = 'home';
$vars['title'] = $vars['site_name'] . ' Home';
$this->addVars($vars);
}
}
As you can see this is extending MyView
class. All your views should extend that class (feel free to rename it) and that is where you can put any common logic that all your views should have access to.
Next we see it’s calling it’s parent constructor and passing through the template engine and variables. Don’t worry too much about this, this is just setting up the templating engine.
The last thing in this file we see is that it’s adding a new variable, ‘page’ and setting it to ‘home’. You can add as many variables as you want here and they will all be available within the front end template. page is a special variable though and must be set in every view. The variable set in page
is used to decide which template to use. In this case it will be using the home.html
template. title
is the variable that will be used for the title of this page. You can see here it’s using one of the default variables set in the configuration ‘site_name’.
Let’s look at the root template www/templates/index.html
. All pages use the same template by default. It’s possible to override this behaviour in the View
class but most of the time you’ll probably want all your pages to have the same header and footer.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{{title}}</title>
</head>
<body>
{{> header}}
{{> page}}
{{> footer}}
</body>
</html>
As you can see this template is extremely simple and you’ll probably want to fill it out much more. Here you can start to see the Mustache templating library in action. {{title}}
will be replaced with the title
set in the view.
In Mustache the >
modifier is used to load a sub template (partial). In Cohesion the partial template name can be overwritten by setting a variable with that name. Since we set the variable page
to home
it will load the www/templates/home.html
template in the middle. But the header and footer weren’t overwritten so they will just use the templates www/templates/header.html
and www/templates/footer.html
. So if you modify the header template it will update the header on every page then we can just change the page
variable to set the main content of the page.
Backend / Services¶
Your main code for all your business logic will go into folders within the src/
directory.
Let’s run through creating a user.
We’ll start by creating a directory src/user/
and adding a User.php
file to it as our DTO:
use \Cohesion\Structure\DTO;
class User extends DTO {
protected $id;
protected $username;
protected $email;
protected $password;
const MIN_USERNAME_LENGTH = 3;
const MAX_USERNAME_LENGTH = 30;
const MIN_PASSWORD_LENGTH = 6;
public function setUsername($username) {
if (strlen($username) < self::MIN_USERNAME_LENGTH) {
throw new UserSafeException('Username must be at least ' . self::MIN_USERNAME_LENGTH . ' characters long.');
} else if (strlen($username) > self::MAX_USERNAME_LENGTH) {
throw new UserSafeException('Username cannot be longer than ' . self::MAX_USERNAME_LENGTH . ' characters.');
}
$this->username = $username;
}
public function setEmail($email) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new UserSafeException('Invalid email address');
}
$this->email = $email;
}
public function getUsername() {
return $this->username;
}
public function getEmail() {
return $this->email;
}
}
That’s all that’s needed for now. The constructor of the parent DTO
class will be able to take in an associative array and call the appropriate mutator functions. And also provides a getVars()
function that will return the protected variables as an associative array.
Next lets create a UserService
at src/user/UserService.php
. The service is the external API for everything other components need to do with “users”. Normally it would include a bunch of authorization code etc but for now let’s just keep it simple.
use \Cohesion\Structure\Service;
use \Cohesion\Auth\AuthException;
class UserService extends Service {
public function getUser($id) {
return $this->dao->getUser($id);
}
public function getUserByUsername($username) {
return $this->dao->getUserFromUsername($username);
}
public function createUser($user) {
$user = $this->dao->getUserFromUsername($user->getUsername());
if ($user) {
throw new AuthException("Username {$user->getUsername()} is already taken");
}
try {
$this->dao->createUser($user);
} catch (DBException $e) {
throw new AuthException('Unable to register new user');
}
return $user;
}
}
The constructor of the parent Service
will create the dao
for us and will look for a class named UserDAO
so let’s create that now. src/user/UserDAO.php
:
use \Cohesion\Structure\DAO;
class UserDAO extends DAO {
public function getUser($id) {
$result = $this->db->query('
SELECT u.id, username, email
FROM users
WHERE id = {{id}}
', array('id' => $id));
if ($row = $result->nextRow()) {
return new User($row);
} else {
return null;
}
}
public function getUserFromUsername($username) {
$result = $this->db->query('
SELECT u.id, username, email
FROM users
WHERE username = {{username}}
', array('username' => $username));
if ($row = $result->nextRow()) {
return new User($row);
} else {
return null;
}
}
public function createUser(&$user) {
$result = $this->db->query('
INSERT INTO users
(username, email, password)
VALUES
({{username}}, {{email}}, {{password}})
', $user->getVars());
$user->setId($result->insertId());
return $user;
}
}
The parent DAO
constructor will set the db
for us so we don’t need to worry about getting the database connection. If we wanted to be able to access other libraries from within the DAO
we can override the constructor and define the extra data accesses we need.
For example if we wanted to use a cache we could add a constructor like this:
use \Cohesion\Structure\DAO;
use \Cohesion\DataAccess\Database\Database;
use \Cohesion\DataAccess\Cache\Cache;
class UserDAO extends DAO {
protected $cache;
public function __construct(Database $db, Cache $cache) {
parent::__construct($db);
$this->cache = $cache;
}
public function getUser($id) {
$userData = $this->cache->get("user_$id");
if (!$userData) {
$result = $this->db->query('
SELECT u.id, username, email
FROM users
WHERE id = {{id}}
', array('id' => $id));
if ($userData = $result->nextRow()) {
$this->cache->save("user_$id", $userData);
}
}
if ($userData) {
return new User($userData);
} else {
return null;
}
}
// ...
}
We don’t need to change any code anywhere else other than making sure that the Cache is set up properly in the configuration files.
Code Structure¶
Cohesion lays out the ground work for an extended MVC set up. I say extended because it goes one step further at separation of responsibilities by splitting the ‘Model’ into the Business Logic, Object Data and Data Access Logic.
For each “object” that you will use in your application you should create an associated service and data access object. For example our User
object will just be a dumb object and all associated business logic should be done within the UserService
. The UserService
will then use it’s DAO
to access and store related user data.
If you are developing an application with many module you can create a service hierarchy where the high level services will be the high level business logic for the module and it will make use of the individual object services for the object specific business logic.
Nothing within any Service, DTO, or DAO should have any idea about the environment in which it’s being used. This means that we should be able to reuse the same services for web application, command line scripts, and anything else that may need to reuse the same business logic such as mobile application back ends etc.
Services - Business Logic¶
Services contain all the business logic and only the business logic for a specific object. I’ve called the business logic section ‘Services’ because I want people to think of them as an independent Service for one portion of the application. The service will contain all the authorisations and validations before carrying out an operation. Services are the entry point for the DAOs, where only the UserService accesses the UserDAO. The UserService can be thought of as the internal API for everything to do with Users.
Data Transfer Objects (DTOs) - Object Data¶
DTOs are simple instance objects that contain the data about a specific object and don’t contain any business logic or data access logic. DTOs have accessors and mutators (getter and setters) and can contain minimal validation in the mutators.
Data Access Objects (DAOs) - Data Access Logic¶
DAOs contain all the logic to store and retrieve objects and data from external data sources such as RDBMSs, non relational data stores, cache, etc. The DAO does not contain any business logic and will simply perform the operations given to it, therefore it’s extremely important that all accesses to DAOs come from the correlating Service so that can perform the necessary checks before performing the storage or retrieval operations. The Service doesn’t care how the DAO stores or retrieves the data only that it does. If later down the line a system is needed to be converted from using MySQL to MongoDB the only code that should ever need to be touched would be to within the DAOs.
Controllers¶
The Controllers are the external facing code that access the input variables and returns the output of the relevant view. The Controller handles the authentication, accesses the relevant Service(s) then constructs the relevant view. It doesn’t contain any business logic including authorisation.
Views¶
Views take in the required parameters and return a rendering of what the user should see. Views don’t perform any logic and don’t do any calls to any function to get additional data. All data that the view will need should be provided to the view from the Controller.
Configuration¶
The configuration is a big part of the backbone of Cohesion. It contains all the information for the Dependency Injection and how each utility should be used.
Configuration files are JSON formatted and can include comments. The configuration files are set up in a cascading fashion where loading subsequent configurations will overwrite just the values that are specified in that config and the unspecified configurations are left unchanged.
The default Cohesion configuration is located at myproject/config/cohesion-default-conf.json
. It is not recommended to make any changes to this file directly as it may make it harder to resolve any conflicts etc if we update it. Instead you should put all your default configurations in the myproject/config/default-conf.json
file. Remember you don’t need to implement all the settings, only add the ones that you want to do differently from the cohesion defaults.
File Structure¶
The configuration is very well structured and documented so make sure you take the time to read the comments in the cohesion default configuration file about what each setting does.
When constructing various objects and libraries they will be given a sub section of the configuration so it’s important to get the structure right.
All objects and libraries will have access to the global
section.
Each Service that you create will get a copy of the application
section. The view and templating library will get the view
section. The database class will use a copy of the data_access.database
. And so on and so forth.
Environment specific configurations¶
Cohesion can load an additional separate configuration for different environments such as different database settings, etc for your dev, staging and production environments. These are just examples you can have separate configuration for whatever different environments you might have. Simply create additional configuration files in the form {environment}-conf.json
. For example, local-conf.json
or production-conf.json
.
Again these are cascaded so you only need to include the settings that are different for that environment and it will get the rest of the settings from your default-conf.json
and the cohesion-default-conf.json
.
Configuration Options¶
global¶
Global configurations are settings that may effect multiple areas of the code. Global should only be used as a last resort if the configuration doesn’t fit in better in any of the other setting sections. Options provided within the global section will be available to all Configurable objects.
Default global settings¶
Key | Type | Default | Description |
---|---|---|---|
global.domain_name | string | ‘www.example.com’ | Domain name of your application. Used for setting link URLs as well as other places.
This will be overwritten by $_SERVER['HOST'] if it’s set but it’s important to
still set this as it will be used in things like sending emails from the command line
etc that don’t have access to the host parameter |
global.production | boolean | false |
Whether or not the current system is being used in production. This is used for things such as setting the error output level as well as other business logic determinant on whether it’s in production or not. |
global.autoloader.cache_key | string | ‘autoloader_cache’ | Cache key used to cache the class paths for your files. |
application¶
The application configuration section is what will be passed to your business logic classes. This is where you should be placing your own configurations for your application settings and should contain all your business rule settings.
Default application settings¶
Key | Type | Default | Description |
---|---|---|---|
class.default | string | null | Default service to use if the ServiceFactory::getService() function isn’t provided
a class name. The default of null means that you must specify the service as
there’s no default. |
class.prefix | string | ‘’ | Prefix used in the service class name before the service name. For example if the
prefix was “Application” and you called ServiceFactory::getService('User') it will
look for a ApplicationUser class. |
class.suffix | string | ‘Service’ | Suffix used in the service class name after the service name. For example with the
default suffix of “Service” when you call ServiceFactory::getService('User') it
will look for a UserService class. |
routing¶
The routing configuration section is used to decided how to deal with incoming requests and decide which Controller should be used to handle it.
We currently don’t provide a way to specify specific routes and how they will be handled. Rather Cohesion works on a simple yet effective default route system.
It will look for the first path segment that is a valid Controller then check if the following path segment is a function of that Controller, if it is then it will call that function with all the following path segments as parameters to that function, if it’s not a valid function then all segments after the Controller will be considered parameters to the Controller’s index function.
Given the path /nothing/something/action/param1/param2?foo=bar
“nothing” is not a Controller and will be ignored. something
matches the controller SomethingController
(based on the class prefix and suffix) and therefore that Controller will be used. action
is a function of SomethingController
therefore the resulting call based on a request to that URI will be: SomethingController->action('param1', 'param2')
. To access the GET and POST parameters you can to use $this->input->get('foo')
;
For the home page (route ‘/
‘) the default controller will be used with the default function.
Default routing settings¶
Key | Type | Default | Description |
---|---|---|---|
class.default | string | ‘Home’ | Default controller to use if no other Controller is found in the path. |
class.prefix | string | ‘’ | Prefix to use when matching the Controller class name with the path. |
class.suffix | string | ‘Controller’ | Suffix to use when matching the Controller class name with the path. The default of
“Controller” means that something will try to match the SomethingController |
function.default | string | ‘index’ | Default function to use within the matched Controller when no other function is found on the path. |
function.prefix | string | ‘’ | Prefix to use when matching the Controller function name with the path. |
function.suffix | string | ‘’ | Suffix to use when matching the Controller function name with the path. For example
if this is set to “Action” and the path had view then it would look for the
viewAction function within the Controller. |
redirects | mappings |
|
Redirects can be used to send a redirect. Each of the keys within the redirects configuration are used as regular expressions to be used to match the path. If the regex matches the page will be redirected to the value here. |
view¶
The view configuration section defines how the views will be generated.
Default view settings¶
Key | Type | Default | Description |
---|---|---|---|
class | Used to define the class names so that the ViewFactory can create the views based on just the base name. Eg, ‘home’ will create a HomeView instance. | ||
class.default | string | ‘’ | Default view to use when no view name is given to the ViewFactory. The default of
'' means that it will default using the prefix and suffix. If you don’t want it
to use any default set this to null. |
class.prefix | string | ‘’ | Prefix used in the view class name before the view name. For example if the prefix
was “My” and you called ViewFactory::createView('page') it will look for a
MyPage class. |
class.suffix | string | ‘View’ | Suffix used in the view class name after the view name. With the default value a call
to ViewFactory::createView('page') will look for the PageView class. |
template.engine | string | ‘CohesionTemplatingCohesionMo’ | The default templating engine is basically an extended version of Mustache with a couple of added functionalities. You can change this to use a different templating engine but the engine must implement the TemplateEngine interface. So if you want to use a different currently unsupported template engine you can wrap it in a new class that implements the interface. |
template.directory | string | ‘www/templates’ | Root template directory containing all your template files. |
template.extension | string | ‘html’ | Extension used on your template files. |
template.default_layout | string | ‘index’ | Default template file to use which contains the root layout of all your pages. |
template.vars | mapping | see file | Set of default variables to be available in every template. |
cache | mixed | { "ttl": 3600 } |
To cache prerendered templates set either a directory or a ttl. If you set the
directory it will store the prerendered templates on disk within that directory. The
directory can either be a relative path to the base directory or an absolute path
starting from ‘/’. Make sure the application server has write access to the
directory. If you don’t set a directory it will use the default system cache and you
can set the ttl for the cache keys. Setting cache to false will disable
caching although this is not recommended. |
cdn | Using Content Delivery Network will usually increase the speed of viewing your site as well as drastically reducing server load as all your static assets will be cached on a third party server that usually have end points all around the globe. | ||
cdn.hosts | array | [] |
Hosts is an array of fully qualified domain names to use. It’s recommended that you use multiple CDN domains as nearly all browsers have a limit of concurrent connections to the same domain. Adding multiple CDN hosts will allow clients to download more assets concurrently. If no hosts are set then CDN will be disabled and all requests will go to the application server. |
cdn.version | Versioning is used to invalidate assets when they change. Because the CDN will cache your static assets they will need a new resource name every time you update your files, such as updates to JavaScript files etc. It’s important that if your CDN asks you whether or not to include the query string you include it. File versions are basically just an MD5 check sum of the file. To improve performance the version is stored in the local cache and is only re-evaluated after the ttl has expired. | ||
cdn.version.cache_prefix | string | ‘asset_version_’ | Prefix to use when caching the versions into the local cache. |
cdn.version.ttl | int | 300 | Expiry of the cached version in seconds. 300 is 5 minutes |
formats | object | see file | These are the accepted formats and the view class that matches them If you want to use your own default view for HTML you should overwrite it in your configuration. Eg, the format ‘plain’ will use the PlainView by default. |
mime_types | object | see file | Mappings of various mime types to the format. You probably shouldn’t overwrite these but you can extend this if want to implement other formats. |
data_access¶
The data_access configuration section contains each of the data access types that your application will use. The key should match a data access interface or class. If a driver is specified it will use that class otherwise it will try to use the setting value.
public SomethingDAO(Database $db)
will use the database setting and instantiate the driver (MySQL).
default data access settings¶
Key | Type | Default | Description |
---|---|---|---|
class.default | string | null |
Default Data Access Object to use when no name is given to the
DAOFactory::getDAO() function. null means that you must supply a DAO name
whenever calling that function. |
class.prefix | string | ‘’ | Prefix for DAO classes. |
class.suffix | string | ‘DAO’ | Suffix for DAO classes. |
database | Database settings. | ||
database.driver | string | ‘MySQL’ | Driver class name used to connect to the database. |
database.host | string | ‘localhost’ | Database host for the main database connection used for writes. |
database.port | int | 3306 | Database port to use when connecting to the host. |
database.user | string | ‘root’ | User used when connecting to the host. |
database.password | string | ‘’ | Database password. |
database.database | string | ‘cohesion’ | Default database used when connected to the server. |
database.charset | string | ‘UTF8’ | Default character set to use. |
database.slave | The slave is used in a master-slave database set up. You can override the user, password and database settings here if you want to use different ones for the slaves, otherwise it will use the same ones. | ||
database.slave.hosts | array | [ 'localhost' ] |
Array of slave hosts to use for read only statements. If none are set then it will just use the same as the main. |
database.slave.user | string | unset | Set this if you want to use a different user to connect to the slaves. The same user will be used for all slaves. |
database.slave.password | string | unset | Set this if you want to use a different password to connect to the slaves. |
database.slave.database | string | unset | Set this if you want to use a different database on the slaves. |
cache | Default cache to use for system caching. | ||
cache.driver | string | ‘APC’ | Class used for system caching. |
object¶
The object configuration section sets up the basic objects. There shouldn’t really be any reason to add anything extra in here as objects are just dumb classes that store data.
default object settings¶
Key | Type | Default | Description |
---|---|---|---|
class.prefix | string | ‘’ | Prefix used for DTO classes. |
class.suffix | string | ‘’ | Suffix used for DTO classes. The default of no prefix or suffix means that the ‘user’ DTO will be the ‘User’ class. |
utility¶
The utility configuration section provides the config used by the various utility classes. Each of these sections are optional and only required if you want to use the utility.
Dependency Injection¶
Dependency Injection simplified¶
If you’re not aware of what dependency injection is you can read about it on Wikipedia. But if you do you might get a bit scared off because they make it sound quite complex when it’s really actually quite simple. The idea is that rather than hard coding the other classes your class will use it takes them in via the constructor parameters or a setter method.
For example rather than in your class creating a MySQL connection to talk to the database you can specify that your class needs an instance of something that implements the Database
interface and then it will be up to the code that calls your class to pass in the instance.
How Cohesion performs Dependency Injection¶
In Cohesion we’ve gone one step further and abstracted away creating the instances anywhere within your code and is instead up to the configuration to supply the information needed to decide how to create instances that implement the interface you need.
So basically you can just say, I need a Cache
library, then in the config you can decide whether to use APC, Memcache, Redis, or whatever. And at any point you can change which type of cache you’re using just by updating your configuration.
The dependency injection relies heavily on PHP Reflection to inspect the object you want to create, look at the parameters the constructor is asking for, get the required library based on the config and pass in an instance. This is the job of the Factories.
Creating a Service¶
The Service Factory is used to create instances of services for your application to use. It makes sure that the service knows who the current user is and passes in the application configuration. When a service is created the Service constructor uses the Data Access Factory to create a DAO for that Service. For example, the UserService
should only ever use the UserDAO
and shouldn’t have access to any other DAOs therefore when you create a UserService
it will set the object parameter dao
to an instance of the UserDAO
.
Creating DAOs¶
DAOs are created using the Data Access Factory. It will inspect the constructor and pass in the accesses it needs. DAOs can reference other DAOs and can specify them in the constructor.
class UserDAO extends DAO {
protected $cache;
protected $locationDao;
public function __construct(Database $db, Cache $cache, LocationDAO $locationDao) {
parent::__construct($db);
$this->cache = $cache;
$this->locationDao = $locationDao;
}
...
}
Creating Utilities¶
If a utility implements the Util
interface you can create instances of that utility using the Util Factory which will construct that utility using the configuration set in the config.
Routing¶
Route matching¶
The current routing doesn’t get specified but instead in inferred by the path, class and function names of the Controllers.
Based on /{directory}/../{controller}/{function}/{var}
. For example /user/list/all
would call UserController->list('all')
.
hyphens (-
) in the path are used as word separators so that it can match the correct capitalisation of controller and function names. /contact-us
will match ContactUsController
.
Using defaults¶
It will also accept variations that use the default values. It will first try to match a controller, if it doesn’t match a controller then it will try to match a function on the default controller. Also if the function is omitted it will use the default function of the selected controller. The defaults can be configured in the config.
By default /
will match the default controller’s default function, HomeController->index()
.
/about
will first look for an AboutController
if it exists it will use the default index()
function on that controller. If that controller doesn’t exist then it will try to use the about()
function on the default HomeController
. If that function doesn’t exist it will throw a NotFound
error.
Parameters¶
The function must be able to accept the number of parameters on the path otherwise it will throw a NotFound
error. If the parameter to the function is optional (has a default value) then that parameter may be left off the path.
For example the path foo/bar/abc/def
could match FooController->bar('abc', 'def')
but if the bar()
function can’t accept two arguments then it will fail.
Here’s an example of a controller class and the url -> call matches.
class FooController extends Controller {
function index($param1, $param2, $param3 = null) {
// ...
}
function bar($param1, $param2) {
// ...
}
}
/foo/abc/def/ghi
->FooController->index('abc', 'def', 'ghi');
/foo/abc/def/
->FooController->index('abc', 'def', null);
/foo/abc
-> 404 NotFound/foo/bar/abc/def
->FooController->bar('abc', 'def');
/foo/bar/abc/def/ghi
-> 404 NotFound/foo/bar/abc
-> 404 NotFound
Note
That last one could actually match FooController->index('bar', 'abc')
but because bar
matched a function it doesn’t fall back to the index. This is important to note so that you take this into consideration when naming your functions and what data you could be expecting.
Sub directories¶
Cohesion will itterate through the URI components and will first try to find a controller with the name of the component, then it will look to see if there’s a directory with that name and move on to the next URI component.
/some/path/foo/bar/abc/def
will match the filecontrollers/some/path/FooController.php
and runFooController->bar('abc', 'def');
Redirects¶
Redirects can be specified in the config. Each of the keys within the redirects configuration are used as regular expressions to match on the path. If the regex matches the page will be redirected to the value here.
For example this will match the default favicon most browsers will look for and redirect to the icon in the assets/images
directory
{
"routing": {
"redirects": {
"^/favicon.ico$": "/assets/images/favicon.ico"
}
}
}
Templating¶
Cohesion uses the Mustache templating language (thanks to @bobthecow‘s implementation) by default and is initialised with a CohesionMo class that set’s up some additional useful functionalities for you. The reason for choosing Mustache was to eradicate the ability to perform any logic within the View. It’s recommended that you read through the Mustache documentation on the Syntax. The implementation of Mustache being used is extremely efficient as it will pre-compile the templates to PHP code and store it in fast access cache.
Additional functionalities¶
Variable partials¶
Partials will check to see if there’s a variable with that name first, if there is then the content of that variable will be used, otherwise it will revert to the standard behaviour of using the name as the partial. This means you have to take this into consideration when naming your variables and partials.
If you want to load a template named header.html then a template who’s name is set by the variable $content then the footer.html template, you can simply do this:
{{> header}}
{{> content}}
{{> footer}}
Site URLs¶
A default lambda is added to generate site URLs for you. Never hard code your URLs or use relative paths. Always use this lambda when generating any links to pages within your site.
<a href="{{#site_url}}about{{/site_url}}">About Us</a>
Asset URLs¶
A default lambda has also been added for generating URLs to your assets. Rather than using the site URL to access your assets you should always use the asset lambda which will allow for using CDNs for your static assets. Even if you don’t wish to use a CDN for now it’s a good practice to still use this lambda for your assets and if no CDN hosts are defined in your config it will just revert to the site URL. Then when you decide you need to relieve the load on your sever you can set up a CDN and just modify your config to point to that.
<script src="{{#asset_url}}{{asset_path.javascript}}main.js{{/asset_url}}"></script>
Asset versioning¶
The asset URLs will also contain a “version” as a parameter in the query string which is basically just an md5 checksum of the file. This is used to invalidate the CDN cache whenever you release an update to any of your static assets. The above example will render as:
<script src="http://cdn.example.com/assets/js/main.js?v=365424d18c0e435388a859592b8f5e3e"></script>
It’s very important that when setting up your CDN you tell it to include the query string in the cache key, otherwise you will have to manually invalidate your assets every time you update them.
Don’t worry though, we’re not recomputing the md5 checksum of every static asset on every page load, these are stored in the local cache (APC) and only updated when the ttl on the cache expires.
Error Handling¶
All errors should throw exceptions and only be caught at a point in the code that is equipped to handle the exception.
Exceptions¶
You should create your own exceptions for your application so that they can be handled appropriately. Cohesion specifies a UserSafeException
. You should use an implemenation of a UserSafeException
for any errors that are safe for the user to see. This includes things such as invalid input etc. The message of UserSafeException
s are displayed back to the user.
Error Output¶
When an exception isn’t caught within the code it will generate an error page. Different exceptions may generate different error Views. A NotFoundException
will show a 404 page, an UnauthorizedException
will show a 403 page, and other exceptions will show a 500 ServerError
page. If the environment is set to ‘production’ non UserSafeExceptions
will just generate a vague error message. But if the environment isn’t ‘production’ then it will show the exception error message as well as the stack trace for easier debugging.
If there’s an uncaught exception in an AJAX call it will determine the expected format and return a valid response in that format. For JSON it will return something similar to:
{
"success": false,
"errors": [
"Server Error"
]
}
Utility Classes¶
Cohesion comes with several extremely lightweight utility classes such as input handling, database interaction, configuration, and many more. More will be added over time and I’m always happy to receive pull requests with additional utilities.
Database Interaction¶
A MySQL database library is included to provide safe interaction with MySQL databases. The database class includes support for master/slave queries, transactions, named variables, as well as many other features.
The Database library isn’t an ORM, it requires you to write SQL statements. This allows for better performance optimization and forces you to think about what SQL will actually be executed.
The database library uses Mustache style named parameters.
For more information on the database library read the documentation at the start of the MySQL.php
file.
Configuration Library¶
A config object class is provided for easy and extendible configuration. The config object will read one or more JSON formatted files and sections of the config can be given to classes to provide either business rule constants or library configurations. It is designed so that you can have a default config file with all the default configurations for your application then you can have a production config file that will overwrite only the variables within that config file such as database access etc.
You can access sub settings using dot notation.
Input Handler¶
An extremely simple input handler is provided. The input handler doesn’t do anything to prevent SQL injection or XSS as these are handled by the Database library and the Views respectively.
Examples¶
I will be adding some examples of using Cohesion for some simple applications soon.
Contributing¶
I would love to hear your feedback on Cohesion and welcome any pull requests. Feel free to raise any issues or merge requests on any of the GitHub repositories
Licence¶
Cohesion is open source and released under the MIT licence.
Code¶
Feel free to checkout the code directly from GitHub.
- The Cohesion Framework repository
- The
cohesion-framework
is the basic starting point for creating new projects and includes thecohesion-core
package. - The Cohesion Core Package repository
- The
cohesion-core
contains the main classes used in the Cohesion framework. - Cohesion Docs repository
- The repository containing this documentation.