getting started  »  popcorn

Popcorn

The Popcorn PHP Micro-Framework is a lightweight REST micro-framework that's built on top of the Pop PHP Framework core components. With it, you can rapidly wire together the routes and configuration needed for your REST-based web application, while leveraging the pre-existing features and functionality of the Pop PHP Framework. It provides a simple layer on top of the main Pop\Application object that allows you to wire up routes and enforce their access based on the request method. By default, it ships with popphp/popphp, popphp/pop-http, popphp/pop-session and popphp/pop-view components.

Basic Use

In a simple index.php file, you can define the routes you want to allow in your application. In this example, closures are used as the controllers. The wildcard route * can serve as a "catch-all" to handle routes that are not found or not allowed.

use Popcorn\Pop;

$app = new Pop();

// Home page: GET http://localhost/
$app->get('/', function() {
    echo 'Hello World!';
});

// Say hello page: GET http://localhost/hello/world
$app->get('/hello/:name', function($name) {
    echo 'Hello ' . ucfirst($name) . '!';
});

// Wildcard route to handle errors
$app->get('*', function() {
    header('HTTP/1.1 404 Not Found');
    echo 'Page Not Found.';
});

The above example defines two GET routes and wildcard to handle failures. We can define a POST route like in this example below:

// Post auth route: POST http://localhost/auth
$app->post('/auth', function() {
    if ($_SERVER['HTTP_AUTHORIZATION'] == 'my-token') {
        echo 'Auth successful';
    } else {
        echo 'Auth failed';
    }
});

$app->run();
curl -X POST --header "Authorization: bad-token" http://localhost:8000/auth
Auth failed

curl -X POST --header "Authorization: my-token" http://localhost:8000/auth
Auth successful

Advanced Use

In a more advanced example, we can take advantage of more of an MVC-style of wiring up an application using the core components of Pop PHP with Popcorn. Let's look at a controller class MyApp\Controller\IndexController like this:

namespace MyApp\Controller;

use Pop\Controller\AbstractController;
use Pop\Http\Server\Request;
use Pop\Http\Server\Response;
use Pop\View\View;

class IndexController extends AbstractController
{

    protected Request  $request;
    protected Response $response;
    protected string   $viewPath;

    public function __construct(
        Request $request = new Request(), Response $response = new Response()
    )
    {
        $this->request  = $request;
        $this->response = $response;
        $this->viewPath = __DIR__ . '/../view/';
    }

    public function index(): void
    {
        $view        = new View($this->viewPath . '/index.phtml');
        $view->title = 'Hello';

        $this->response->setBody($view->render());
        $this->response->send();
    }

    public function hello($name): void
    {
        $view        = new View($this->viewPath . '/index.phtml');
        $view->title = 'Hello ' . $name;
        $view->name  = $name;

        $this->response->setBody($view->render());
        $this->response->send();
    }

    public function error(): void
    {
        $view        = new View($this->viewPath . '/error.phtml');
        $view->title =  'Error';

        $this->response->setBody($view->render());
        $this->response->send(404);
    }

}

and two view scripts, index.phtml and error.phtml, respectively:

<!DOCTYPE html>
<!-- index.phtml //-->
<html>

<head>
    <title><?=$title; ?></title>
</head>

<body>
    <h1><?=$title; ?></h1>
<?php if (isset($name)): ?>
    <p>Your name is <?=$name; ?></p>
<?php endif; ?>
</body>

</html>
<!DOCTYPE html>
<!-- error.phtml //-->
<html>

<head>
    <title><?=$title; ?></title>
</head>

<body>
    <h1 style="color: #f00;"><?=$title; ?></h1>
    <p>Sorry, that page was not found.</p>
</body>

</html>

Then we can set the app like this:

use Popcorn\Pop;

$app = new Pop();

$app->get('/', [
    'controller' => 'MyApp\Controller\IndexController',
    'action'     => 'index',
    'default'    => true
])->get('/hello/:name', [
    'controller' => 'MyApp\Controller\IndexController',
    'action'     => 'hello'
]);

$app->run();

The default parameter sets the controller as the default controller to handle routes that aren't found. Typically, there is a default action in the controller, such as an error method, to handle this.

Routes

As in the above examples, you can use the API to define routes directly:

  • get($route, $controller)
  • head($route, $controller)
  • post($route, $controller)
  • put($route, $controller)
  • delete($route, $controller)
  • trace($route, $controller)
  • options($route, $controller)
  • connect($route, $controller)
  • patch($route, $controller)

Or, you can use the standard Pop\Application route configuration array for Popcorn by nesting the routes inside another array level, with the array keys being the defined method:

// Routes configuration
$config = [
    'routes' => [
        'get' => [
            '/users' => [
                'controller' => 'MyApp\Http\Controller\UsersController',
                'action'     => 'index'
            ]
        ],
        'post' => [
            '/users' => [
                'controller' => 'MyApp\Http\Controller\UsersController',
                'action'     => 'create'
            ]
        ],
        'patch' => [
            '/users/:id' => [
                'controller' => 'MyApp\Http\Controller\UsersController',
                'action'     => 'update'
            ]
        ],
        'delete' => [
            '/users/:id' => [
                'controller' => 'MyApp\Http\Controller\UsersController',
                'action'     => 'delete'
            ]
        ]
    ]
];

$app = new Pop($config);

Custom Methods

If your web server allows the configuration of custom HTTP methods, Popcorn supports that and allows you to register custom HTTP methods with the application.

use Popcorn\Pop;

$app = new Pop();
$app->addCustomMethod('PURGE')
    ->addCustomMethod('COPY');

$app->purge('/image/:id', function($id){
    // Do something with the PURGE method on the image URL
});

$app->copy('/image/:id', function($id){
    // Do something with the COPY method on the image URL
});

$app->run();

Then you can submit requests with your custom HTTP methods like this:

curl -X PURGE http://localhost/image/1
curl -X COPY http://localhost/image/1

API

Here is an overview of the available API within the Popcorn\Pop class:

  • get($route, $controller) - Set a GET route
  • head($route, $controller) - Set a HEAD route
  • post($route, $controller) - Set a POST route
  • put($route, $controller) - Set a PUT route
  • delete($route, $controller) - Set a DELETE route
  • trace($route, $controller) - Set a TRACE route
  • options($route, $controller) - Set an OPTIONS route
  • connect($route, $controller) - Set a CONNECT route
  • patch($route, $controller) - Set a PATCH route
  • setRoute($method, $route, $controller) - Set a specific route
  • setRoutes($methods, $route, $controller) - Set a specific route and apply to multiple methods at once
  • addToAll($route, $controller) - Set a specific route to all methods at once
  • any($route, $controller) - Set a specific route to all methods at once (alias to 'addToAll')
  • addCustomMethod($customMethod) - Add a custom method
  • hasCustomMethod($customMethod) - Check if the object has a custom method

The setRoutes() method allows you to set a specific route and apply it to multiple methods all at once, like this:

use Popcorn\Pop;

$app = new Pop();

$app->setRoutes('get,post', '/login', [
    'controller' => 'MyApp\Controller\IndexController',
    'action'     => 'login'
]);

$app->run();

In the above example, the route /login could display the login form on GET, and then accept the login form submission on POST.