Question is not what is Moo, but why is Moo?
There is time that every PHP developer has to create its own framework. Most of them are crap, this one is no exception.
So, Moo, the framework, is a spawn of PHP evil created for making life and debugging miserable. It took a couple of hours to create, but can take days to find out what and why works or does not. Like most microframeworks, Moo is quite close to HTTP world, made of bunch of pure PHP classes with no external dependencies. Just router, request, response, boom done.
Of course, it would be easier, faster and wiser to just use any other off shelf framework, but there it is, Moo!
Why not.
Seriously, for 99% of time you should not use this framework. Don't do it for sake of yours and other developers wellbeing and mental health.
The only exception to that I can think of, is when you consider using raw .php scripts somewhere on a server. If that is the case, Moo can actually be useful.
Looking for some decent PHP framework? Go learn Symfony, Laravel or anything that actually has any community around. This one does not have any. Actually, you can try writing your own micro framework, just like Moo to learn and validate your PHP skills.
If you really have to, here you go, just use composer.
composer config minimum-stability dev
composer require loskoderos/moo:dev-main
Simplest Moo application.
<?php
$moo = new Moo\Moo();
$moo->get('/', function () {
return "Hello, this is Moo!";
});
$moo();
This is the sample Moo app with more features presented like state container, plugins and parametrized routing.
<?php
$moo = new Moo\Moo();
// Use moo as service container, inject dependency.
$moo->bookService = new App\BookService();
// Override the before hook to set Content-Type header.
$moo->before = function () use ($moo) {
$moo->response->headers->set('Content-Type', 'application/json');
};
// Override the after hook to serialize output to JSON.
$moo->after = function () use ($moo) {
$moo->response->body = json_encode($moo->response->body);
}
// Custom plugin.
$moo->findBookById = function ($bookId) use ($moo) {
return $moo->bookService->find($bookId);
}
// Define index handler.
$moo->get('/', function () {
echo "Hello, this is Moo bookstore!";
});
// Define `GET /books/<id>` handler.
$moo->get('/books/(\d+)', function ($bookId) use ($moo) {
return $moo->findBookById($bookId);
});
// Run Moo.
$moo();
Moo supports native PHP templates, however you can extend the base class and render templates with any other engine.
See the website
example in examples
.
use Moo\Moo;
use Moo\Template;
$moo = new Moo();
// Create template renderer.
$moo->template = new Template(__DIR__ . '/templates', [
'foo' => 'bar'
]);
// Custom template plugin.
$moo->template->date = function () {
return date('Y-m-d');
};
// Index page handler.
$moo->get('/', function () use ($moo) {
return $moo->template->render('index.phtml', [
'hello' => 'world'
]);
});
Sample PHP template code.
<div>foo = <?php echo $foo ?></div>
<div>hello = <?php echo $hello ?></div>
<div>date = <?php echo $this->date() ?></div>
Some may need a to write a posh code, if you need style you may try the following.
<?php
use Moo\Moo;
class Application extends Moo
{
public function __construct()
{
parent::__construct();
$this->get('/', [$this, 'index']);
}
public function index()
{
echo 'Hello World';
}
}
$app = new Application();
$app();
Test it and make your colleagues happy.
<?php
use PHPUnit\Framework\TestCase;
use Moo\Request;
class ApplicationTest extends TestCase
{
public function testIndex()
{
$request = new Request();
$request->method = 'GET';
$request->uri = '/';
$app = new Application();
$app->flush = null; // You don't want output buffer flush in the unit tests.
$app($request);
$this->assertEquals(200, $app->response->code);
$this->assertEquals('Hello World', $app->response->body);
}
}
You can actually use one instance of Moo as a callback in another Moo.
<?php
use Moo\Moo;
$usersMoo = new Moo();
$usersMoo->get('/users/(\d+)', function ($id) {
// ...
});
$booksMoo = new Moo();
$booksMoo->get('/books/(\d+)', function ($id) {
// ...
});
$moo = new Moo()
$moo->route('/users/.*', $usersMoo);
$moo->route('/books/.*', $booksMoo);
$moo();
Note that, when nested, only the top level flush
is being used therefore the output from $moo
is sent only once.
There are some examples in the examples
directory.
To run them you can use builtin PHP server.
php -S 0.0.0.0:8080 examples/hello-world/index.php
The goal of Moo is simplicity, flexibility and ease of use.
Moo is written in PHP and is closure based. From design perspective it is a front controller, the Moo\Moo
class exposes a set of standard HTTP methods to bind routing handlers as closures. Additionally, Moo acts as a state container and can be extended with plugins.
All Moo components reside in the PSR-4 Moo
namespace. The main component is the Moo\Moo
class. There are three models: Moo\Request
, Moo\Response
, Moo\Route
and the Moo\Router
that works as dispatcher.
Moo does output buffering, so you can simply output with echo or return a serializable value in the closure.
Here is what happends when you call $moo(...)
:
flowchart TD
before["Pre request hook"]
dispatch["Match route"]
error["Run error hook if no route matched request"]
after["Post request hook"]
flush["Flush output"]
before --> |$moo->before| dispatch
dispatch --> |$moo->get, $moo->post, ...| error
error --> |$moo->error| after
after --> |$moo->after| flush
You can bind handlers to standard HTTP methods:
- GET:
$moo->get(...)
- HEAD:
$moo->head(...)
- POST:
$moo->post(...)
- PUT:
$moo->put(...)
- DELETE:
$moo->delete(...)
- CONNECT:
$moo->connect(...)
- OPTIONS:
$moo->options(...)
- TRACE:
$moo->trace(...)
- PATCH:
$moo->patch(...)
You can match multiple methods using $moo->route(...)
.
Routes are matched from the first to the last. If no route matches the request, the error handler is called $moo->error(...)
.
Route can be parametrized with regular expressions.
$moo->post('/orders/(\d+)/items/(\d+)', function ($orderId, $itemId) use ($moo) {
return $moo->orderService->findOrderItem($orderId, $itemId);
});
There are two handlers pre and post request, the before and the after.
$moo->before = function () {
echo "Hey I'm the before hook";
};
$moo->after = function () {
echo "Hey I'm the after hook";
};
$moo();
By default exceptions are handled by the error handlers, that also works when no route matches request.
$moo->error = function(\Exception $exc) use ($moo) {
$moo->response = new Response([
'code' => $exc->getCode() > 0 ? $exc->getCode() : 500,
'message' => StatusCode::message($exc->getCode()),
'body' => $exc->getMessage()
]);
};
The Moo\Request
class consists of:
- method
- uri
- headers
- query
- post
- files
- body
Request object $moo->request
is created before before
with the values taken from PHP global variables, $_SERVER
, $_GET
, $_POST
, $_FILES
.
Request body is empty by default! You can override that with ini:
$moo->before = function () ($moo) {
$moo->request->body = file_get_contents('php://input');
};
The Moo\Response
class consists of:
- body
- headers
- code
- message
Response object $moo->response
is created before before
and can be modified during the request lifecycle. Response body is not serialized, it is assumed you serialize it before flush
in after
.
You can use Moo to keep your application state.
$moo->something = 123;
$moo->myState = [
'config' => [
'host' => 'localhost',
'port' => 1234
]
];
You can extend Moo with custom functions.
$moo->foobar = function () {
return 123;
};
$moo->foobar();
By default all output is buffered and then flushed at the end of $moo()
execution.
You can override that behaviour by setting custom flush handler.
$moo->flush = function () use ($moo) {
header('HTTP/1.1 ' . $moo->response->code . ' ' . $moo->response->message);
foreach ($moo->response->headers as $name => $value) {
header($name.': '.$value, true);
}
echo $moo->response->body;
};
Moo is unit tested, just run make test
.
Contributions are welcome, please submit a pull request.
MIT