Tipsy is an MVW (Model, View, Whatever) PHP micro framework inspired by AngularJS. It provides a very lightweight, easy to use interface for websites, rest apis, and dependency injection.
If you prefer, you can download a Single Page version of the docs.
See Installation for more information.
composer require tipsyphp/tipsy
use Tipsy\Tipsy;
$tipsy = new Tipsy;
Routes can be closures, classes, or class instances. Here we create a route using a closure, and assign the internal $Scope and $View properties.
$tipsy->router()
->when('/', function($Scope, $View) {
$Scope->user = 'Devin';
$View->display('home');
});
All views are PHTML View Templates. Create a file called home.phtml
<div class="content">
<h1>Welcome <?=$user?>!</h1>
</div>
Once you have defined your stuff you can start the process like below.
$tipsy->run();
For more detailed examples see Examples.
Tipsy will either read your $_SERVER['REQUEST_URI']
, or a $_REQUEST['__url']
variable. In order to tell Apache to forward all pages to your index file, create a .htaccess file. Note that the ?__url=$1
is optional.
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php?__url=$1 [L,QSA]
You will need an nginx config file that looks something like this
location / {
try_files $uri @rewriteapp;
}
location @rewriteapp {
rewrite ^(.*)$ /index.php last;
}
If you are using templates, I highly recommend enabling PHP short tags. It will save you lots of time when working with variables. For more information, see PHTML View Templates.
To enable short tags, add the following line to your php.ini
short_open_tag = On
Or you can add it using an Apache .htaccess
php_value short_open_tag On
See Installing Composer if you have not already installed composer.
Run the following command:
composer require tipsyphp/tipsy --prefer-dist
Or omit the --prefer-dist
if you want to get the tests
directory as well.
If you prefer you can add it directly to your composer.json file and then run composer install
"require": {
"tipsyphp/tipsy": "dev-master"
}
Include Tipsy using composer's autoload
require_once __DIR__ . '/../vendor/autoload.php';
use Tipsy\Tipsy;
Read Server Config for more info on how to setup your web server.
Install Composer if you haven't already. Composer is a really easy to use PHP dependency & package management tool. I prefer to install globally so you can use the command composer from anywhere in your terminal.
On OSX
cd /usr/local/bin
curl -sS https://getcomposer.org/installer | php
mv composer.phar composer
Everyone else can Download Composer here, and Read how to install.
The Tipsy app can be accessed either by it's static methods, object methods, or a combination of both.
$t = new Tipsy\Tipsy;
$r->router()->home(function() {
echo "it's good to be home";
});
$t->run();
Tipsy\Tipsy::router()->home(function() {
echo "it's good to be home";
});
Tipsy\Tipsy::run();
or
use Tipsy\Tipsy;
Tipsy::router()->home(function() {
echo "it's good to be home";
});
Tipsy::run();
You probably only need this if you are doing weird stuff.
use Tipsy\Tipsy;
$tipsy = Tipsy::app();
$tipsy->router()->home(function() {
echo "it's good to be home";
});
Tipsy::run();
The App object exposes and forwards the get
, post
and when
methods to its $this->router()
method for shorthand route creation. All arguments passed theses functions are passed on to the router.
$app->get('user/:name', function($Params) {
echo $Params->name;
});
or
$app->when([
'route' => 'user/:name',
'method' => 'get',
'controller' => function($Params) {
echo $Params->name
}
]);
See Routes for more information.
Tipsy accesses much of its configuration from a single config object
var_dump($tipsy->config());
echo $tipsy->config()['db']['host'];
You can set the config in a config file, multiple config files, or at any point during execution.
$tipsy->config(['view' => [
'path' => 'views',
'layout' => 'awesome-layout'
]]);
Tipsy supports both INI and YAML file formats. YML parsing requires a yml parsing library. You can install one using symfony/yaml
from Packagist, or the yaml_parse_file
function using PECL.
// Tell tipsy to read the config files
$tipsy->config('production.ini');
$tipsy->config('plugins.yml');
// use wildcard selectors
$tipsy->config('config/*.ini');
$tipsy->config('config/*.yml');
$tipsy->config('config/*');
You can combine both INI and YML and it will Recursive Replace the config arrays.
In
db:
user: username
pass: password
driver: mysql
view:
path: views
[db]
host=localhost
[view]
layout=default
Out
{
"db": {
"user": "username",
"pass": "password",
"driver": "mysql",
"host": "localhost"
},
"view": {
"path": "views",
"layout": "default"
}
}
The router supports any amount of routes, methods, and can be chained.
$tipsy->router()->get('hello', function() {
echo 'World';
});
See Route Methods for more information. For more control on routes see Advanced Routing.
A route controller can be a closure function, class name, tipsy controller, class instance, or anonymous class.
$tipsy->router()->when('some/page', new class() extends Tipsy\Controller {
public function init() {
echo 'Page';
}
});
See Controller Types for more information.
Routes accept colon delimited params, as well as regex routes.
$tipsy->router()->when('user/:name', function($Params) {
echo $Params->name;
});
See Route Params for more information.
The router methods return the router object to allow chaining.
$tipsy->router()
->home(function() {
echo 'Sup home boy';
})
->when('cats', function() {
echo 'Every kind of cat.';
})
->otherwise(function() {
echo '404';
});
All controllers support Dependency Injection of both User Defined Services and Built in Services.
$tipsy->router()
->when('user/:id', function($Params, $View) {
$View->display('home', ['user' => $Params->id]);
});
See Dependency Injection for more information.
Routes can be mapped from one route to another.
$tipsy->router()
->when('item/edit/:id', function($Params) {
echo $Params->id;
})
->alias('item/:id/edit', 'item/edit/:id');
See Route Aliases for more information.
Tipsy will either read your $_SERVER['REQUEST_URI']
, or a $_REQUEST['__url']
variable.
For more information on how to set up routing, see Server Config
The router object sends all method calls through the when
method using either a 1 or 2 parameter call.
Accepts 2 parameters: route and controller. Used for defining a route that matches any method.
$tipsy->router()->when('hello',function() {
echo 'World';
});
Accepts 1 parameter: controller. Used for specifying the home page. This is equivalent to specifying an empty ''
or '/'
when method.
$tipsy->router()->home(function() {
echo 'Honey, Im home!';
});
Accepts 2 parameters: route and controller. Used for defining a route with a specific method. You can use absolutely any method. Even made up ones.
$tipsy->router()->post(function() {
echo 'Yay! Posting stuff!';
});
$tipsy->router()->bacon(function() {
echo 'This is a bacon method!';
});
Accepts parameter: controller. If no match is found, the otherwise function will be called. Used for default, or 404 pages.
$tipsy->router()->otherwise(function() {
echo '404';
});
Route controllers can be closure functions, class names, controllers, class instances, or anonymous classes.
$tipsy->router()
->when('/about', function() {
echo 'This is us!';
});
// Define the Class
class ClassController extends Tipsy\Controller {
public function init() {
echo 'This is a class';
}
}
// Set the route
$tipsy->router()
->when('about', [
'controller' => 'LibraryController'
]);
// Define the Controller
$tipsy->controller('InternalController', function() {
echo 'This is an internal Controller';
});
// Set the route
$tipsy->router()
->when('about', [
'controller' => 'InternalController'
]);
// Define the Controller
class InstanceController extends Tipsy\Controller {
public function init() {
echo 'This is a instance';
}
}
// instantiate object
$instance = new InstanceController;
// Set the route
$tipsy->router()
->when('about', [
'controller' => $instance
]);
$tipsy->router()
->when('some/page', new class() extends Tipsy\Controller {
public function init() {
echo 'Page';
}
});
Routes accept params similar to Express Routes.
$tipsy->router()->when('user/:name', function($Params) {
echo $Params->name;
});
Route Aliases map params based on the Param Name
$tipsy->router()->alias('group/:project/user/:name', 'user/:name');
Routes can also accept a full regex. When a regex is used, params is an array of matches.
$tipsy->router()->when('/^api\/user\/(.*)\/details$/i', function($Params) {
echo $Params[1]; // the user name of the service
});
Regex is especially useful when catching files that need to render.
$tipsy->router()->when('/\.scss$/i', function($Request) {
echo $Request->path(); // the requested file
});
Aliases allow you to map one or multiple existing routes to new routes. Params are also mapped. Each From Param is mapped to the To Param you provide.
$tipsy->router()
->when('item/edit/:id', function($Params) {
echo $Params->id;
})
->alias('item/:id/edit', 'item/edit/:id');
$tipsy->run();
When requesting item/1/edit it will echo 1.
Aliases also take over the methods of the parent, so you can have multiple separate methods using the same alias.
$tipsy->router()
->post('item/edit/:id', function($Params) {
// save the item
})
->get('item/edit/:id', function($Params) {
// retrieve the item
})
->alias('item/:id/edit', 'item/edit/:id');
$tipsy->run();
Tipsy allows easy reference of a bunch of different services and models used within itself using Dependency Injection.
For a list of all available services, see Services.
$tipsy->router()
->when('user/:id', function($Params, $Scope, $View) {
$Scope->user = $Params->id;
$View->display('home');
});
// Define the User
$tipsy->service('User', [
sup => function() {
return 'Sup '.$this->user;
}
]);
// Set the route
$tipsy->router()
->when('user', function($User) {
echo $User->sup();
});
If the first argument of when
is an array you can create more customized routes, including multiple comma separated methods.
$tipsy->router()
->when([
'route' => 'update',
'method' => 'post,put',
'controller' => function() {
echo 'Updating content...';
}
]);
Here are a few different ways to define the home page using the methods described above.
$tipsy->router()
->when('', function() {
echo 'Here I am';
});
is the same as
$tipsy->router()
->when('/', function() {
echo 'Here I am';
});
is the same as
$tipsy->router()
->home(function() {
echo 'Here I am';
});
is the same as
$tipsy->router()
->when([
'route' => '',
'method' => '*',
'controller' => function() {
echo 'Here I am';
}
]);
Tipsy exposes it's objects as services using Built in Services.
You can define your own services using User Defined Services
Services in Tipsy are static objects. They are accessible in the same ways as Built in Services. Services can be arrays, closures, classes or class instances.
$tipsy->service('Service', [
stuff => function() {
// some stuff
}
]);
Using a closure allows for the use of Dependency Injection as you can see with this $View
example.
$tipsy->service('Service', function($View) {
$service = [
stuff => function() use ($View) {
// some stuff
}
];
return $service;
);
class Service extends Tipsy\Service {
public function stuff() {
// stuff
}
}
$tipsy->service('Service');
class Service extends Tipsy\Service {
public function stuff() {
// stuff
}
}
$tipsy->service('Service', new Service);
$tipsy->service('Service', new class() extends Tipsy\Service {
public function stuff() {
}
});
Tipsy uses Dependency Injection to allow easy access to services. Services act as static models that are the same instance throughout the Tipsy instance.
Allows access to the view renderer. For more information see Views.
$tipsy->router()
->when('', function($View) {
$View->display('home');
});
The scope of the view. All variables are passes by reference to allow two way data binding. For more information see Views.
// Router PHP
$tipsy->router()
->when('chat', function($Scope, $View) {
$Scope->message = 'Hello!';
$View->display('chat'); // will output "<b>Hello!</b>"
});
<?/* Chat PHTML */ ?>
<b><?=$message?></b>
Direct access to the Tipsy object.
$tipsy->router()
->when('debug', function($Tipsy) {
var_dump($Tipsy->config());
});
Direct access to the current route
$tipsy->router()
->when('debug', function($Route) {
var_dump($Route);
});
Access to route params. Anything that starts with ":" will populate this service.
$tipsy->router()
->when('user/:id', function($Params) {
echo $Params->id;
});
The HTTP request service. Access to request variables or JSON data. Data is accessible by it's properties/
Methods
base()
- returns the basepath of the scripthost()
- returns the hostnameloc($x)
- returns the path piece from the pathpath()
- returns the path of the requesturl
- returns the full path including hostname$tipsy->router()
->get('/', function($Request) {
echo $Request->var;
});
Db provides basic database connectivity.
$tipsy->config([db => [url => 'mysql://user:pass@host:port/database']]);
//or
$tipsy->config([db => [
host => 'host',
user => 'user',
pass => 'pass',
port => 'port',
database => 'database',
driver => 'mysql'
]);
$tipsy->db()->query('select * from stuff');
Middleware allows you to intercept or execute services before the app runs. Use the run
function as you would construct. Middleware supports the same definitions as Services.
$tipsy = new Tipsy\Tipsy;
$tipsy->middleware('LoginService', [
'run' => function() {
if (!$_SESSION['user']) {
// login
} else {
$this->user = 'devin';
}
}
]);
$tipsy->router()->home(function($LoginService) {
echo $LoginService->user;
});
$tipsy->run();
This will create the middleware and run it immediately.
$tipsy->middleware(function($Request) {
if ($Request->loc() == 'api') {
// do special login stuff for any /api/* url
}
});
Tipsy comes with a phtml template system. It supports layouts as well as cascading include directories.
By default the path is the current directory. But you can set it to anything you want.
// Set the path of the view
$tipsy->config(['view' => [
'path' => 'views'
]]);
// Set up the router
$tipsy->router()
->when('/', function($View, $Scope) {
$View->display('home', ['user' => 'Devin']);
});
<?/* view located in views/home.phtml */?>
Welcome <b><?=$user?></b>!
By default Tipsy will look for layout.phtml in your view path, but you can set it to anything you want.
// Set the path of the view and layout
$tipsy->config(['view' => [
'path' => 'views',
'layout' => 'awesome-layout'
]]);
<?/* layout located in views/awesome-layout.phtml */?>
<title>Awesome</title>
<h1>Awesome.sauce</h1>
<div class="content">
<?=$this->content?>
</div>
The view service supports both display, which outputs, and render, which returns the output to a variable.
$tipsy->router()
->when('/chat/reply', function($View) {
$output = $View->render('reply');
echo json_encode([
'time' => date('Y-m-d H:i:s'),
'html' => $output
]);
});
All variables are passed by reference so you can change anything in, and out of the templates.
// Router PHP
$tipsy->router()
->when('chat', function($Scope, $View) {
$Scope->message = 'Hello!';
$View->display('chat'); // will output "<b>Hello!</b>"
echo $Scope->message; // will output "Goodbye!"
});
<? /* chat.phtml */ ?>
<b><?=$message?></b>
<? $message = 'Goodbye!'?>
By accessing the $this variable you can access the current $View service.
Renders a file and returns as string
<?=$this->render('file', ['user' => 'devin'])?>
Calls the render
function and prints the output
<? $this->display('file', ['user' => 'devin']); ?>
Renders a file with the current scope and returns the output
<?=$include('file', ['user' => 'devin'])?>
You can set config either using the methods above, or using an config file. For more information on config, see Config.
// Tell tipsy to read the config file
$tipsy->config('config.ini');
; File located at config.ini
[view]
path=views
layout=layout
stack[]=main/english
stack[]=main/spanish
stack[]=cobrand/english
stack[]=cobrand/spanish
.phtml differs from .php in that it is primarily for templating and views rather than scripts or code. The language itself is the same, however in phtml, it is mostly html, rather than php. This helps separate your types of code so that frontend and backend developers can better find what they need.
Because most of the code is html, it is common to use Short Tags.
<?php echo $something ?> // A normal opening tag, output, and closing
<?=$something?> // A short tag, output, and closing
For more information on how to enable them if you don't already have them, see PHP Short Tags
Reading html and looking for closing braces, it can be a pain. I recommend using the alternative syntax to make the code more readable.
<? if (1 == 1) { ?>
Duh.
<? } ?>
<? if (1 == 1) : ?>
Duh.
<? endif; ?>
// assign the data
$friends = ['bob','jim','katie'];
// loop through the data and output
echo '<div class="friends">';
foreach ($friends as $friend) {
echo '<b>'.$friend.'<b> is my friend.<br>';
}
echo '</div>';
$Scope->friends = ['bob','jim','katie'];
$View->display('friends');
<div class="friends">
<? foreach ($friends as $friend) : ?>
<b><?=$friend?><b> is my friend.<br>
<? endforeach ; ?>
</div>
Each time a template is rendered, a new scope is created. This scope is accessible by the current template, and any template that it renders inside of it.
The display
and render
methods of View
accept 2 parameters, the template filename, and an optional scope. Scope can also be accessed using Dependency Injection.
$tipsy->when('friends', function($Scope, $View) {
$Scope->friends = ['bob','jim','katie'];
$View->display('friends');
});
or
$tipsy->when('friends', function($View) {
$View->display('friends', ['friends' => ['bob','jim','katie']]);
});
$data = [
'user' => [
'name' => 'devin,
'friends' => [
'bob',
'jim',
'katie'
]
]
];
$View->display('home', $data);
Hello <?=$user->name?>!
<? foreach ($user->friends as $friend) : ?>
<?=$include('friend', ['user' => $friend])?>
<? endforeach ; ?>
<?=$user?>
Tipsy provides a simple ORM for SQL.
Load is called on class instantiation, or when called using object notation. Accepts either an id, an array, or ab object to construct.
Load the object by $id from the database
$User->load($id);
or
User::o($id);
Create a new object with an array. You may also pass an object instead of an array in replace of any of the 3 examples below.
$user = $User->load([
name => 'devin'
]);
or
$user = new User([
name => 'devin'
]);
or
$user = User::o([
name => 'devin'
]);
Send a query to retrieve a list of items. This will return a Looper object.
$User->query('select * from user where name like ', ['devin%']);
or
User::q('select * from user where name like ', ['devin%']);
Note that both the Resource object and the Looper object have the method get
. You can always safely use $object->get(0)
regardless of object type.
Returns the database id of the object
$user = $User->load(1);
echo $user->dbId(); // prints 1
Returns the objects properties as an array
$user = new User([
name => 'devin'
]);
var_dump($user->properties()); // dumps an array with the user devin
By default, returns the object's properties. This method is called internally by json()
, and is best used when dumping different data for different users. See the example below for more info.
$user = new User([
name => 'devin'
]);
var_dump($user->exports()); // dumps an array with the user devin
Resource allows use of $resource->json()
and json_encode($resource)
interchangeably. Both these method call the exports
method.
$user = new User([
name => 'devin'
]);
echo $user->json(); // prints the json output of the resources properties
Not yet fully implemented
Not yet fully implemented
Resources are defined using Tipsy's Service interface. See User-Defined-Services for more information.
$tipsy->service('User', [
_id => 'id',
_table => 'user'
]);
// create the User model based on the resource class
$tipsy->service('User', [
_id => 'id',
_table => 'user'
]);
// load the user when the route is visited and display the user name
$tipsy->when('user/:id', function($Params, $User) {
echo $User->load($Params->id)->name;
});
// create the User model
class User extends \Tipsy\Resource {
public function exports() {
$props = $this->properties();
// properties viewable by anyone
$public = ['id', 'name', 'location', 'website', 'image'];
// properties viewable only by the logged in user
$private = ['email', 'gender', 'auth'];
if (Tipsy::middleware('Session')->user()) {
$public = array_merge($public, $private);
}
foreach ($props as $key => $prop) {
if (!in_array($key, $public)) {
unset($props[$key]);
}
}
return $props;
}
public function __construct($id = null) {
$this->idVar('id')->table('user')->load($id);
}
}
// load the user when the route is visited and display the users properties
$tipsy->when('user/:id', function($Params, $User) {
echo $User->load($Params->id)->json();
});
Factory provides an in memory cache of objects with a type, key, value store system. This is enabled by default for Resource.
When objects are loaded from the database using the load method they are automatically added to the factory cache.
$user = User::o(1);
$user->name = 'new name';
$user = User::o(1); // load the item from memory instead of another database call
echo $user->name; // prints "new name"
The factory method gets or sets an object by classname, or passed object
$object = new User([
id => 1
]);
$u = $tipsy->factory($object);
// or
$u = $tipsy->factory('User', $object);
Disable in php
$tipsy->config(['tipsy' => ['factory' => false]]);
or in a config file
tipsy:
factory: false
Looper provides an interface similar to Iterator with some Resource specific features.
Looper allows the use of $loop->each()
, foreach ($loop as $item)
, or while($loop->valid()
interchangeably.
foreach ($loop as $item) {
//item
}
or
$loop->each(function() {
// $this
});
or
while ($loop->valid()) {
// $loop->current()
$loop->next();
}
Get grabs an item in the loop by index.
$loop->get(0);
Looper allows the use of $loop->json()
or json_encode($loop)
interchangeably.
echo $loop->json();
or
echo json_encode($loop);
Set properties of all items in a loop
$loop = new \Tipsy\Looper([
(object)['a' => 1],
(object)['a' => 1],
(object)['a' => 2]
]);
$loop = $loop->set('a', 1);
echo $loop->json(); // returns 3 items with a set to 1
Filtering a Loop will return all objects who's properties match the properties of a passed argument.
$loop = new \Tipsy\Looper([
(object)['a' => 1],
(object)['a' => 1],
(object)['a' => 2]
]);
$loop = $loop->filter([
'a' => 1
]);
echo $loop->json(); // returns the first 2 items as json
Not works the same as filter except backwards
$loop = new \Tipsy\Looper([
(object)['a' => 1],
(object)['a' => 1],
(object)['a' => 2]
]);
$loop = $loop->not([
'a' => 1
]);
echo $loop->json(); // returns the last item in json
Slice works similar to array_slice
$loop = new \Tipsy\Looper([
(object)['a' => 1],
(object)['a' => 1],
(object)['a' => 2]
]);
$loop = $loop->slice(1, 2);
echo $loop->json(); // returns the last 2 items in json
When nesting loops, parent returns the immediate parent loop. This is most useful when using filters
$loop = new \Tipsy\Looper([
(object)['a' => 1],
(object)['a' => 1],
(object)['a' => 2]
]);
$loop = $loop->filter(['a' => 1])->set('a', 2)->parent();
echo $loop->json(); // returns a list of 3 items with a set to 2
You can see some more advanced examples in tests/LooperTest.php.
use Tipsy\Tipsy;
$app = new Tipsy;
$app->get('home', function($View) {
$View->display('home', [user => 'crystal']);
});
<h1>Hello <?=$user?></h1>
$app->post('drink/:id', function($Params, $Request, $Maitai) {
$Maitai
->load($Params->id)
->serialize($Request->request())
->save();
echo $Maitai->json()
});
POST /drink/1?rating=5&name=maitai
All example code is provided in their own repo with 1 click deployments provided free by Heroku.
Officially supported plugins
The goal of Tipsy is to provide a very lightweight PHP framework, capable of handling just about everything.
Tipsy is benchmarked by kenjis php framework benchmark and currently rated at the fastest pure php framework as of 11/29/2015.
See Contributing for info on issues, pull requests, and contact.
Tipsy is created and maintained by Devin Smith.