toadsuck / core
Front controller and other core libraries used by the Toadsuck project.
Requires
- php: >= 5.4
- aura/router: 2.*@dev
- fuelphp/config: dev-master
- illuminate/database: 4.2.*@dev
- league/plates: 1.0.*
- prelude/prelude-dsn: dev-master
- symfony/http-foundation: 2.5.*@dev
Requires (Dev)
- phpunit/phpunit: 3.7.*
- squizlabs/php_codesniffer: 1.*
README
Core libraries for the Toadsuck Project
- Routing - Aura.Router
- HTTP Abstraction - Symfony\HttpFoundation
- Templates - Plates Native PHP Templates
- Database Abstraction - Illuminate\Database
- Configuration Management - FuelPHP\Config
- Unit Tests - PHPUnit (of course, why use anything else?)
Learn more at The Toadsuck Project or see Toadsuck.Skeleton for a reference implementation.
Installation
Installation of this package is easy with Composer. If you aren't familiar with the Composer Dependency Manager for PHP, you should read this first.
If you don't already have Composer installed (either globally or in your project), you can install it like this:
$ curl -sS https://getcomposer.org/installer | php
Create a file named composer.json someplace (usually in the root of your project) with the following content.
{ "require": { "toadsuck/core": "dev-master" } }
Suggested Directory Structure
- Your application resides in
src/
and has a base namespace ofExample\Project
- Your controllers are in
src/controllers/
and have a namespace ofExample\Project\Controllers
- Your controllers extend
Toadsuck\Core\Controller
- Your controllers extend
src/
config/
local/
config.php
test/
prod/
config.php
routes.php
controllers/
Home.php
models/
views/
home/
index.php
layouts/
default.php
vendor/
Composer's installation directory
web/
index.php
composer.json
Front Controller
Your web/index.php
serves as the front controller and contains the following content:
<?php # File: web/index.php namespace Example\Project; // Change this to whatever base namespace you are using in your project. $app_dir = dirname(__DIR__); // Use Composer's autoloader. require_once $app_dir . '/vendor/autoload.php'; // Pass a couple options to our dispatcher. $opts = ['app_dir' => $app_dir, 'namespace' => __NAMESPACE__]; $app = new \Toadsuck\Core\Dispatcher($opts); // Dispatch the request. $app->dispatch();
This will turn control of dispatching the request to the Toadsuck.Core dispatcher.
Routing
Routing is handled by the Aura Router.
Some sensible default routes are provided out of the box in the dispatcher.
$router->add(null, null); $router->add(null, '/'); $router->add(null, '/{controller}'); $router->add(null, '/{controller}/{action}'); $router->add(null, '/{controller}/{action}/{id}');
The following will all call the index()
method of your Home
controller.
This one will also call index
, but will also pass the value "1" to the index method.
Likewise, you can call the bar()
method of the Foo
controller like this:
If you want to supply different routes, just create a config
directory under source with a
file named routes.php
that defines the routes you need.
src/
config/
routes.php
For more information on routing options, see https://github.com/auraphp/Aura.Router.
Configuration
The configuration manger is built on FuelPHP\Config
Basic Configuration Usage
// Load our primary config file. $this->config->load('config'); $something = $this->config->get('something'); // You can load any configuration file... $this->config->load('database');
Multiple Environment Configuration Support
The configuration manager supports different configs for different environments (local, dev, test, prod, etc).
To activate per-environment configs, create a sub-directory of src/config
named the same as your environment.
This directory should contain configuration files with any items from the main config you want to override in that environment.
Example:
src/config/test/database.php
You can tell the app which environment you are running in by modifying the content of src/config/environment
to contain the name of your active environment.
Then load your config as you normally would. The configuration items will be merged between the default config environment-specific overrides.
See the FuelPHP Docs for more information on configuration management.
Extra Configuration Helper Methods
getBaseUrl()
Returns the absolute url to the base of your application without the filename (ex. index.php). Accepts an optional path to append to the base url as an argument.
var_dump($this->config->getBaseUrl()); // http://example.com/ var_dump($this->config->getBaseUrl('image.jpg')); // http://example.com/image.jpg var_dump($this->config->getBaseUrl('home/index')); // http://example.com/home/index
getSiteUrl()
Returns the absolute url to the base of your application, including the filename (ex. index.php). Accepts an optional path to append to the site url as an argument.
var_dump($this->config->getSiteUrl('home/index')); // http://example.com/index.php var_dump($this->config->getSiteUrl('home/index')); // http://example.com/index.php/home/index
Templates
Templates are powered by the Plates Project, which is part of The League of Extraordinary Packages.
Within this project, all themes and layouts live in src/views
.
Basic Template Usage
class Home extends Controller { public function __construct() { // Set our default template. $this->template->layout('layouts/default'); } public function index() { // Set some variables for all views. $this->template->page_title = 'Toadsuck Skeleton'; // Render and Display the home/index view, passing a variable named "heading". $this->template->output('home/index', ['heading' => 'Congratulations, it worked!']); // Same as above, but return the rendered content instead of displaying. // $content = $this->template->render('home/index', ['heading' => 'Congratulations, it worked!']); } }
I've extended the standard Template class from Plates in 2 important ways:
-
- I've added the
output()
method so you can display the rendered view without anecho
if you like.
- I've added the
-
- All variables are escaped before rendering/displaying the content to prevent cross site scripting.
If there are variables you don't want auto-escaped (example: pre-rendered HTML), you can prevent escaping by calling unguard('param_name')
.
$this->template->unguard('html'); $this->template->output('home/index', ['html' => '<p>Some <strong>markup</strong>.</p>']);
Prefill
I've added some functionality to the Template class for easily getting default values in views when the variable doesn't exist.
class Home extends Controller { public function __construct() { // Set our default template. $this->template->layout('layouts/default'); } public function index() { // Grab our prefill content from the request and mass assign. $this->template->setPrefill($this->input->get()); // Or we can pull the prefill content from session. $this->template->setPrefill($this->session->get('prefill')); // Render and Display the home/index view. $this->template->output('home/index'); } }
Then in our view:
<!-- Prefill from the 'foo' variable if it exists. If not, it will default to null. -->
<input type="text" name="foo" value="<?=$this->prefill('foo')?>" />
<!-- Prefill from the 'bar' variable if it exists. If not, it will default to 'some default value'. -->
<input type="text" name="bar" value="<?=$this->prefill('bar', 'some default value')?>" />
See the Plates Project documentation for more information on template usage.
HTTP Abstraction
HTTP Abstraction is provided by Symfony\HttpFoundation. This provides useful things like:
- Object-Oriented access to
$_GET
,$_POST
,$_SESSION
,$_COOKIE
, etc. - Internal/External Redirects
- JSON/JSONP Responses
namespace Example\Project\Controllers; use Illuminate\Database\Capsule\Manager as Model; use Toadsuck\Core\Controller; use Toadsuck\Core\Database as DB; use Toadsuck\Skeleton\Models\Widget; class Home extends Controller { // Internal Redirect (another resource in our app) public function internalRedirect() { $this->redirect('home/foo'); # Foo method of the Home Controller. } // External Redirect public function externalRedirect() { $this->redirect('http://www.google.com'); } // Output data json-encoded with proper headers. public function getJson() { $data = (object) ['items' => ['foo', 'bar']]; $this->json($data); } // Output data json-encoded with proper headers and callback. // Default callback name is 'callback' public function getJsonp() { $data = (object) ['items' => ['foo', 'bar']]; $this->jsonp($data, 'callback'); } }
$_GET and $_POST
The Core Controller has a reference to the httpFoundation Request
object, but I find the syntax for accessing $_GET
and $_POST
attributes less than ideal. So, I've built a wrapper around the httpFoundation Request
object to make the syntax friendlier.
Examples:
# Symfony way to access an attribute from $_POST $foo = $this->request->request->get('foo'); # Toadsuck way to access an attribute from $_POST $foo = $this->input->post('foo'); # Symfony way to access an attribute from $_GET $foo = $this->request->query->get('foo'); # Toadsuck way to access an attribute from $_GET $foo = $this->input->get('foo'); # Symfony way to access the entire $_POST array $post = $this->request->request->all(); # Toadsuck way to access the entire $_POST array $post = $this->input->post(); # Symfony way to access the entire $_GET array $get = $this->request->query->all(); # Toadsuck way to access the entire $_GET array $get = $this->input->get();
null
is returned when trying to access a non-existent attribute from get/post. Pass a 2nd parameter to $this->input->get()
or $this->input->post()
to serve as the default value if you want something other than null
.
// Returns 'foo' $foo = $this->input->post('nonexistent', 'foo');
In the default Request
object, you have to pass true
as the 3rd argument to $this->request->query->get()
in order to access "deep" array keys. This is defaulted to true
in my override.
$bar = $this->input->get('foo[bar]');
See the Symfony Docs for more information on the HttpFoundation component.
Database
Database abstraction is handled by Illuminate\Database
Your model:
# File: src/models/Widget.php namespace Example\Project\Models; use Illuminate\Database\Eloquent\Model; class Widget extends Model { public $timestamps = false; # Aren't using the default timestamp columns }
Instead of extending Eloquent\Model directly, you can extend Toadsuck\Core\Model (which extends Eloquent) to get easier access to the query builder.
# File: src/models/Widget.php namespace Example\Project\Models; use Toadsuck\Core\Model; class Widget extends Model { public $timestamps = false; # Aren't using the default timestamp columns public static function search($params = null) { $query = self::queryBuilder(); if (array_key_exists('firstname', $params)) { $query->where('firstname', $params['firstname']); } if (array_key_exists('lastname', $params)) { $query->where('lastname', $params['lastname']); } return $query->get(); } }
See tests/resources/models/Captain for a working example.
Initialize the database before you try to use it.
use Toadsuck\Core\Database as DB; /* DSN can be pear-style DSN string: mysql://username:password@host/database OR an array of connection params $defaults = [ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'mysql', 'username' => 'root', 'password' => null, 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => null ]; */ DB::init($dsn);
Once your database has been initialized, you can call your ORM models from anywhere.
// Get all widgets: $widgets = \Example\Project\Models\Widget::all()->toArray(); foreach ($widgets as $widget) { var_dump($widget['type']); var_dump($widget['size']); var_dump($widget['color']); }
See the Laravel Eloquent Documentation for more info on the ORM.
You also have access to the fluent query builder.
use Illuminate\Database\Capsule\Manager as QueryBuilder; $result = QueryBuilder::table('captains')->where('lastname', 'Kirk')->get();
See the Laravel Database Documentation for more info on the fluent query builder.
Multiple Database Connection Support
To initialize Eloquent with multiple database connections, you can pass an array of connection params to DB::init()
.
# DSN Strings $dsn = ['default' => 'mysql://username:password@hostname/primarydb', 'otherdb' => 'mysql://username:password@hostname/otherdb']; DB::init($dsn);
# Connection setting array $dsn = [ 'default' => [ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'primarydb', 'username' => 'username', 'password' => 'password' ], 'otherdb' => [ 'driver' => 'mysql', 'host' => 'localhost', 'database' => 'otherdb', 'username' => 'username', 'password' => 'password' ] ]; DB::init($dsn);
Then in your model, specify the connection to use.
<?php use Illuminate\Database\Eloquent\Model; class Widget extends Model { public $timestamps = false; public $connection = 'otherdb'; }