Bone MVC Framework v3 released

vX.Y.Z – Major.minor.patch – or, BC breaks, features, hotfixes! The best software would be 1.āˆž.x, as that would mean an absolute tonne of new features have been added, without breaking any backwards compatibility!

Having said all of that, I would now like to present my latest BC break, and announce the release of Bone MVC Framework v3.0 šŸ˜€

https://bonemvc.delboysplace.co.uk/en_PI

What’s new? Quite a lot. Try it out! We (I) ripped out the old Bone Router and replaced it with league/route, a PSR-15 middleware router based on Fastroute! We replaced Pimple with delboy1978uk/barnacle, a PSR-11 wrapper for Pimple (Pimple has a PSR-11 wrapper but it doesn’t implement the interface correctly and so doesn’t work. Fabien Potencier has went in a huff with the PHP-Fig group so I didn’t bother sending him a Pull Request šŸ˜). Both of these improvements have allowed for a far more flexible and modular approach to site building, as you will see if you try it out!

Ok then, let’s see it in action

The recommended approach is to use Docker, as this means you can get the identical dev enironment that I do, regardless of your operating system. If you don’t wish to take this approach, you can set a virtualhost and database yourself.

Docker VM dev environment setup

To get the Docker VM working, make sure you have Git, VirtualBox (or VMWare), and Docker installed. Once you do, open a terminal window. We need to run a one off command, telling Docker to create a VirtualBox VM which will be your new Docker Machine.

docker-machine create --driver virtualbox default

You now have a Docker Machine. So, the usual process when you would like to begin coding ais as follows:

docker-machine start

Now it’s started, run this for every tab you open in the terminal to set up environment vars in your terminal

eval $(docker-machine env)

Installing Bone MVC Framework

By default, we use a fake domain https://awesome.scot, so sudo nano /etc/hosts and add the following:

awesome.scot 192.168.99.100

Now if you cd into wherever you wish to store the project (~/Sites in my case), we can clone the framework skeleton project:

git clone https://github.com/delboy1978uk/bonemvc
cd bonemvc

Inside the skeleton files, you’ll see a docker-compose.yml, and a build folder. If your’e nosey, have a look and you’ll see how it works! The VM comes with Apache, PHP 7.3, XDebug and a stack of PHP modules, Composer, Mariadb, Mailhog, self signed SSL certificate, and most of the stuff you need. Lets start it up.

docker-compose up

This will take a few minutes the first time you do this, but thereafter should be a lot faster, even with different Bone MVC projects in different folders.

At this point, your terminal is now tailing the logs from each of your services, mail, php, apache, mariadb, and so we need another tab open as we want to log into to the Linux VM to run composer. Remember when you open a new tab, you need to run the eval command again to get the environment variables.

eval $(docker-machine env)
docker-compose exec php /bin/bash

At this point, you’ll notice a slight change in your shell’s look. We are now logged into the server! Lets install the dependencies and we are ready to rock!

eval $(docker-machine env)
docker-compose exec php /bin/bash
composer install

Once composer is finished doing it’s thing, we are ready to browse to the project! Open https://awesome.scot in your browser, and you should see the following!

Okay great, so how is it all structured?

As per most projects these days, the main entrypoint is public/index.php, and all public assets are installed in public/. Bone use the environment variable APPLICATION_ENV to differentiate between your dev setup and staging, production, etc.

The build folder is part of the Docker setup as mentioned earlier, so can be ignored.

The config folder allows for subfolders based on the APPLICATION_ENV, which will override the config files sitting directly under config/. You can add as many .php files into these folders that you like.

The data folder containsa variety of things. There is a cache storage folder (delboy/bone-cache coming soon), a logs folder (delboy1978uk/bone-log coming soon), a proxies folder (for doctrine entity proxies if using delboy1978uk/bone-doctrine), a translations folder with several languages pre-configured (use poedit to edit these files), and an uploads folder.

The migrations folder is use to store database migrations if using delboy1978uk/bone-doctrine.

The src/ folder contains your applications modules, and currently only contains one module, App.

Inside src/App, you will see a class called AppPackage.php, look inside!

All packages implement RegistrationInterface, and any packages containing controller routes will also implement RouterConfigInterface. addToContainer() allows you to create object factories so you can inject any dependencies. In the AppPackage, the Controller requires the view engine so it can render pages, so we create a controller factory and pull the view from the container (view is set up automatically by Bone):

    /**
     * @param Container $c
     */
    public function addToContainer(Container $c)
    {
        $c[IndexController::class] = $c->factory(function (Container $c) {
            $view = $c->get(PlatesEngine::class);
            return new IndexController($view);
        });
    }

The addRoutes() method tells the router which actions in which controller to call.

    /**
     * @param Container $c
     * @param Router $router
     * @return Router
     */
    public function addRoutes(Container $c, Router $router): Router
    {
        $router->map('GET', '/', [IndexController::class, 'indexAction']);
        $router->map('GET', '/learn', [IndexController::class, 'learnAction']);

        return $router;
    }

If you have Doctrine entities, return true in the hasEntityPath() method, and return the path to the Entity folder in the getEntityPath() method.

Controllers now just take in a PSR-7 request, and return a PSR-7 Response. However we can wrap the request and response in middleware! See league/route docs for more info.

For a bit of fun, I’ll show you a module with a Doctrine entity, and just how quickly you can add a new feature! In the VM CLI, type the following:

composer require delboy1978uk/bone-doctrine
composer require delboy1978uk/bone-gentest

You now have the doctrine and my test entity package in the vendor folder.

Open config/packages.php, and add the two packages.

<?php

use BoneMvc\Module\App\AppPackage;
use BoneMvc\Module\BoneMvcDoctrine\BoneMvcDoctrinePackage;
use Random\Developer\JediPackage;

return [
    'packages' => [
        AppPackage::class,
        BoneMvcDoctrinePackage::class,
        JediPackage::class,
    ],
    'viewFolder' => 'src/App/View'
];

Ok, so we have entities need migrated to the database. In the VM CLI, type the following:

migrant diff
migrant migrate

You’ll notice that migrant is just Doctrine migrations, but tweaked for usage with Bone MVC. Now you have migrated, head over to https://awesome.scot/jedi

You now have a demo module with an index list page, add edit and delete functionality, with form filtering and validation and rendering done via delboy1978uk/form. Have a look through the module code to see how to set everything up!

Please play around and have fun with it, and feel free to leave any comments!

Advanced Paginator with Zend_Db_Table

Make your pagination EASY! Whether using Zend Framework’s MVC, or just the library classes!

By now you might have read by now that half the folk that were using Zend_Paginator have been using it wrong. Ā They would get their rowset from the table, and then create a paginator and pass in the rowset. Sure, it works, but you have just fetched ALL the rows first, and Paginator displays only the records in question. That is a huge waste of resources!

What SHOULD be happening is that you pass a select() object to the paginator, to which it will figure out the offsets and limits.

Anyway, pretty much all my db classes were returning either rowsets or rows depending on the method. What i needed was for it to return the select. So I had an idea. Auto pagination!

Rather than your db classes extending Zend_Db_Table, we will extend our own abstract class which will Ā itself extend Zend_Db_Table.

Our class will have a couple of properties; Ā a flag for pagination on/off, the page number, and the num per page. It will also have a few Ā methods, as you can see below.

<?php

namespace AA\Repository;

use Zend_Db_Table_Abstract as Table;
use Zend_Db_Table_Select as Select;
use Zend_Paginator as Paginator;
use Zend_View_Helper_PaginationControl as PaginationControl;

abstract class AbstractRepository extends Table
{
 /** @var bool */
 private $paginator_flag = false;
 /** @var int */
 private $page = 1;
 /** @var int */
 private $num_per_page = 30;

 /**
 * @param bool $bool
 * @return $this
 */
 public function setPaginatorFlag($bool)
 {
 $this->paginator_flag = $bool;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setPage($no)
 {
 $this->page = $no;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setNumPerPage($no)
 {
 $this->num_per_page = $no;
 return $this;
 }

 /**
 * @param Select $select
 * @return Paginator
 */
 private function getPaginator(Select $select)
 {
 PaginationControl::setDefaultViewPartial('NavControls.phtml');
 $paginator = Paginator::factory($select);
 $paginator->setCurrentPageNumber($this->page);
 $paginator->setItemCountPerPage($this->num_per_page);
 return $paginator;
 }

 /**
 * @param Select $select
 * @return \Zend_Db_Table_Rowset_Abstract|Paginator
 */
 protected function getResult(Select $select)
 {
 if($this->paginator_flag == true)
 {
 return $this->getPaginator($select);
 }
 return $this->fetchAll($select);
 }
}

(If you aren’t using namespaces in your code, you can tweak it to the old style.)

TheĀ setPaginatorFlag($bool) turns paging on or off. setPage($no) and
setNumPerPage($no) are self explanitory. getPaginator(Select $select) returns a paginator from the select object. Ā The interesting method is getResult(Select $select). It checks wether we asked for a paginator. If so it calls getPaginator, if not it does a plain fetchAll.

Have a look at a simple DB Class which now uses the auto paginator:

namespace AA\Repository;


class Competitors extends AbstractRepository
{
 protected $_name = 'branch_competitors';
 protected $_rowClass = 'AA\Entity\Branch\Competitors';


 public function getCompetitors($bid)
 {
 return $this->fetchRow('bid = '.$bid);
 }

 public function getAllCompetitors()
 {
 $select = $this->select()
 ->order('bid DESC');
 return $this->getResult($select);
 }
}

The interesting parts here are the extends AbstractRepository, and return $this->getResult($select). We now use getResult so that it can decide whether to give you a rowset or a paginator.

In your views, you can now just echo it out to give you your paginator! (Of course, you will have your pagination view partial set! see manual for details)

However if you are using Zend Framework libraries without using the MVC component, we can still use Zend Paginator without concerning ourselves with Zend_View etc. I created a view helper class with a static render method, accepting the paginator and the current url:

namespace AA\View\Helper;

use Zend_Paginator;


class Paginator 
{
 public static function render(Zend_Paginator $paginator,$url)
 {
 $url = parse_url($url);
 parse_str($url['query'],$result);
 $class = null; //for now
 if ($paginator->getPages()->pageCount)
 {
 $html = '<div class="pagination"><ul>';
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->first;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="first" href="'.$url['path'].'?'.$query.'">
 first
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">first</a></li>';
 }
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->previous;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 previous
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">previous</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->next;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="next" href="'.$url['path'].'?'.$query.'">
 next
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">next</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->last;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 last
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">last</a></li>';
 }
 $html .= '</ul></div>';
 }
 else
 {
 $html = null;
 }
 return $html;
 }
}

So on a plain ol’ PHP page (that’s able to autoload the classes, if not, require them or look into composer), here’s all you would need (my current url in this example is “/?id1=-competitors”):

$page = ($_GET['page']) ? filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) : 1;
//set pagination on for this query
$db->setPaginatorFlag(true)
   ->setPage($page)
   ->setNumPerPage(30);
//get paginator/rowset
$competitors = $db->getAllCompetitors();
//show paginator
echo \AA\View\Helper\Paginator::render($competitors,'/?id1=competitors');
foreach($competitors as $competitor)
{
    //row stuff here
}

Awesome! Now you need never again have to muck around with paginators, whether using Zend Frameworks MVC or not! Have fun!