Ptero

Ptero is a fast and flexible model-view-controller framework for PHP. It allows you to generate models, views, controllers, and routes instantly over the command-line and automatically provides a strong, clean structure for running web apps.

Think wearing a giant robot exo-skeleton with lasers and missile launchers while crushing the puny challenges of archaic PHP in a two-hour-long feature film directed by Michael Bay. It's that good.

It has the good stuff

Features include:

  • RESTful routing
  • Sensible templates: layouts and views separate
  • RoR-esque code generation
  • PHP-ActiveRecord for ORM: No SQL needed!
  • Installs dependencies quickly and easily with Composer
  • Built in Ruby: Who doesn't love Ruby?

It takes care of the bad stuff

Don't worry about:

  • annoying PHP syntax errors
  • copy-pasting or otherwise repeating code
  • SQL, SQL injection, escaping strings, etc
  • URLs that look ugly
  • PHP being stupid in general

How do I do it?

To start using Ptero, make sure your PHP command-line tool is up-to-date, then run the following:

$ gem install ptero

Now make an application:

$ ptero new Blog

There! Your web application is seeded and ready. Now you can start generating files.

$ cd Blog
$ ptero generate page hippo
$ ptero route /hippo Hippo

Now run an apache server with root directory Blog/www, and navigate to http://localhost/hippo to see a fully-formed page.

(I really should have a built-in server command, but making Ruby run PHP is a mess. If someone wants to write up a cross-platform WEBrick server for PHP, I would be glad to incorporate it)

Onward!

Now for the best part. Let's take a look at the generated code.

Here's the default controller, Blog/php/controllers/HippoController.php

/*
 *
 * HippoController.php
 * ===================
 * This controller does ...
 *
 */

namespace Blog\Controllers;


class HippoController extends ApplicationController {

    // Handle GET requests, use 'public function post()' to handle POST
    public function get() {
        // Render template
        $this->render();
    }

}

The controller inherits from ApplicationController, giving it all sorts of useful functionality, most notably the render() method.

Calling $this->render(); with (or without) an array mapping of arguments will automatically render the template of the same name as the controller, in this case Blog/views/hippo.html.twig.

Here it is:

{% extends 'application.html.twig' %}

{% block head %}
    <!-- HTML head code produced by view: hippo -->
    <script type="text/javascript" src="/assets/js/hippo.js"></script>
    <link rel="stylesheet" type="text/css" src="/assets/css/hippo.css" />
{% endblock %}

{% block content %}
    <!-- HTML content code produced by view: hippo -->
    <h2>View: Hippo</h2>

{% endblock %}

This template is written in Twig. Click here to learn the basics of Twig.

Notice that the template has only a few lines of code, and that it extends a layout, called application.html.twig.

In this way, global layout changes can be made to the global application template, and individual changes made to individual pages.

For example, let's try doing some arbitrary server-side work and sending it to the client:

// HippoController.php

class HippoController extends ApplicationController {

    // Handle GET requests, use 'public function post()' to handle POST
    public function get() {
        // sum the numbers 1-10
        $sum = 0;
        for($i=1;$i<10;$i++) {
            $sum += $i;
        }
        $this->render(array(
            'sum' => $sum
        ));
    }

}

the template:

{% extends 'application.html.twig' %}

{% block head %}
    <!-- HTML head code produced by view: hippo -->
    <script type="text/javascript" src="/assets/js/hippo.js"></script>
    <link rel="stylesheet" type="text/css" src="/assets/css/hippo.css" />
{% endblock %}

{% block content %}
    <!-- HTML content code produced by view: hippo -->
    <h2>The sum is: {{sum}}</h2>

{% endblock %}

Now our code will print the $sum variable passed to it by the server.

Routing

We already did some routing with the $ptero route command, but routes are also stored in a file and editable by hand. Let's open Blog/php/routes.php

// Application route maps
$application->route(array(

      '/hippo' => 'Blog\Controllers\HippoController',
));

Look! The route we made is there! It maps the string '/hippo' to the controller class Blog\Controllers\HippoController.

You can also print all current routes by running

$ ptero routes

We can try using RESTful parameters in our application and passing them to controllers. Try this:

$ ptero generate page rhino
$ ptero route /rhino/:number Rhino

Now edit php/controllers/RhinoController.php:

namespace Blog\Controllers;


class RhinoController extends ApplicationController {

    // Handle GET requests, use 'public function post()' to handle POST
    public function get($number) {
        $number = intval($number);
        $result = $number * $number;
        $this->render(array(
            'result' => $result
        ));
    }

}

and make the template print our result:

{% block content %}
    <!-- HTML content code produced by view: rhino -->
    <h2>View: Rhino</h2>
    <div>
        Result: {{ result }}
    </div>

{% endblock %}

Now navigate to http://localhost/rhino/74

Yay! Our application can compute squares!

Routes in the routes.php file support a full range of regular expressions, although stubs such as :number and :alpha can be useful. Learn more at https://github.com/anandkunal/ToroPHP

DATABASES!!!

Ptero wouldn't be complete without Databases, though, (as-of-yet) we have not incorporated the ability to generate them through the command-line tool.

You'll have to bring your own SQL server. Sorry. Try MAMP.

Ptero uses PHP-ActiveRecord Databases, which are as easy as pie to use with a few minutes' practice. Take a look.

The first step of using Ptero databases is to configure your application. Open config/config.php:

<?php
/*
 * config.php
 * return array of configuration values
 *
 *
 *
 */


return array(
      'templates_path' => 'views',

      'controllers_path' => 'php/controllers',

      'models_path' => 'php/models',



    'database' => array(
        'name'     => 'database-name',
        'user'     => 'username',
        'password' => 'password',
        'server'   => 'localhost'
    )
)
?>

Change the values 'database-name', 'username', 'password', and 'server' to the configuration of your database so that PHP-ActiveRecord knows where to look.

Now let's generate another page. We'll assume that we have a table called Elephants in our database that has a primary-key int "id", a string "name", and another int "age."

$ ptero generate page elephant
$ ptero generate model elephant
$ ptero route /elephants/:number Elephant

In php/controllers/ElephantController.php:

<?php

/*
 *
 * ElephantController.php
 * ======================
 * This controller does ...
 *
 */

namespace Blog\Controllers;
use \Blog\Models as DB; // Make sure we can access Models

class ElephantController extends ApplicationController {

    // Handle GET requests, use 'public function post()' to handle POST
    public function get($id) {
        $this->db_connect('development'); // Connect to our database, in the mode specified. 'development' is the default value. 

        $id = intval($id);
        $elephant = DB\Elephant::find($id);

        $name = $elephant->name;
        $age = $elephant->age;
        $this->render(array(
            'name' => $name,
            'age' => $age
        ));
    }

}


?>

In views/elephant.twig:

{% extends 'application.html.twig' %}

{% block head %}
    <!-- HTML head code produced by view: elephant -->
    <script type="text/javascript" src="/assets/js/elephant.js"></script>
    <link rel="stylesheet" type="text/css" src="/assets/css/elephant.css" />
{% endblock %}

{% block content %}
    <!-- HTML content code produced by view: elephant -->
    <h2>View: Elephant</h2>
    <h2>Name: {{ name }}</h2>
    <h2>Age: {{ age }}</h2>

{% endblock %}

With this code in place, a request to http://localhost/elephants/n will output the information of elephant with id=n.

In this same way, you can create new Elephants and save them to the database by running something akin to:

  $dumbo = new \Blog\Models\Elephant();
    $dumbo->name = 'dumbo';
    $dumbo->age = 25;
    $dumbo->save();

In the post() method of a controller.

Inverse Commands

Each "create" command has an inverse command to undo it.

This "destroy" command will delete or undo a create operation.

USE WITH CARE ON UNCOMMITTED CHANGES.

Here are some common inverse commands:

$ ptero new Slideshow               # Create the Slideshow application
$ cd Slideshow

$ ptero generate controller Index
$ ptero remove controller Index     # remove is the inverse of generate

$ ptero route /asdf/:alpha.jpg Tree
$ ptero unroute /asdf/:alpha.jpg    # unroute is the inverse of route


$ ptero destroy                     # destroy is the inverse of new, it will destroy a named directory or the current one

Conclusion

Ptero is still in a phase of development, so please contact @LuminousRubyist, the creator and maintainer of this project, for any questions, comments, complaints, insults, or profound observations about everyday life.

Contributing

  1. Fork it ( https://github.com/luminousrubyist/ptero/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Thank you

Thank you to all the libraries, and services that make this possible: