Upgrade PHP 5.5 to PHP 7.1 on CentOS 6.9 - Recklessly

I wasn't planning to upgrade PHP today, but in order to use Symfony 4.0, I had to.

First, I wanted to get all the PHP RPMs

sudo yum list installed php55u* | grep php | cut -f1 -d' ' | tr -d '.i686' | tr "\n" ' ' | sed "s/55/71/g" > php

Next, I removed all the PHP 5.5 RPMs:

sudo yum remove php55*

Then I edited the output file (php) and added a sudo yum install at the beginning of the file

So I could use

source php

There were a few more RPMs I needed, after roaming about the web for a bit, these were the commands that I ran

sudo yum install pear1u
sudo yum install php71u-json
sudo yum install libmemcached-devel
sudo pecl install memcached

The recklessly part of this is that I confess I did not check ... anything. My existing Symfony 3.3.10 application comes up and lets me log in. I haven't checked more than that.

Good luck!

DataTables - Adding Buttons to a FixedHeader

I was using both the FixedHeader and Buttons extensions of DataTables and I needed the buttons to remain visible when the user scrolled.

The solution I chose was to move the buttons DOM into the table head.

Since the buttons are in a div and thead is part of a table, a tr is used with a td that has a colspan of the entire table.


var dtButtons = document.querySelector(".dt-buttons");
var row, cell;
row = document.createElement("tr");
cell = document.createElement("td");
cell.id = "magic-button-row";
cell.setAttribute("colspan", /*** Put the number of columns in your table here ***/);
cell.appendChild(dtButtons);
row.appendChild(cell);
document.querySelector("#display-table thead").insertBefore(row, document.querySelector("tr:first-of-type"));

There is a tiny bit of CSS, too.

#magic-button-row {
    padding: 0;
    margin: 0;
    text-align: left;
}

This post courtesy of Game Creek Video.

Revised Dijit/Tree Menu for Symfony

This is the final implementation for the tree menu I'm using for a Symfony application.

The first approach wrote the JSON for the menu store into the template. This was a little awkward and as the application grew, the amount of content increased.

Next, I had an idea that I could create a multi-tiered tree which would have a static top tier, but include content in the lower levels. The value of this was that the user could navigate directly to content, for example the details of a client. Lazy loading was used to limit the number of requests and size of responses. It worked. But it was slow. And I realized that as the amount of data increased, it would get slower. In addition, navigating through three or more levels on a tree is inefficient. So, I wanted something that was faster and streamlined.

Revised Dijit/Tree Menu for Symfony

The first change I made was to add an autocomplete search box, which allows the user enter the name of the item they need. It could be the name of a person, a barcode or a company. The search code isn't complete.

Then I rewrote the JsonRenderer and the MenuStoreController such that there is only a static menu delivered via Ajax.

services.yml

These are the service configurations.


    app.admin.menu_store_controller:
        class: AppBundle\Controller\Api\Admin\Common\MenuStoreController

    app.menu_renderer:
        class: AppBundle\Menu\JsonRenderer
        arguments: [ "@twig",  "knp_menu.html.twig", "@knp_menu.matcher", {"translator": "@translator" }]
        tags:
            - { name: knp_menu.renderer, alias: json }

MenuStoreController

The MenuStoreController calls the KnpMenuBuilder, then the JsonRenderer and delivers the result as JSON to the client.


<?php

namespace AppBundle\Controller\Api\Admin\Common;

use FOS\RestBundle\Controller\FOSRestController;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\View;

class MenuStoreController extends FOSRestController
{
    /**
     * @View()
     */
    public function getAdminmenuAction( Request $request )
    {
        $this->denyAccessUnlessGranted( 'ROLE_ADMIN', null, 'Unable to access this page!' );
        $adminMenu = $this->get( 'app.menu_builder' )->createAdminMenu( [] );
        $renderer = $this->get( 'app.menu_renderer' );
        return array_values( $renderer->render( $adminMenu ) );
    }

}

JsonRenderer

The JsonRenderer iterates through the menu and creates the data for the dijit/Tree.

<?php

namespace AppBundle\Menu;

use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\MatcherInterface;
use Knp\Menu\Renderer\RendererInterface;

class JsonRenderer implements RendererInterface
{

    /**
     * @var \Twig_Environment
     */
    private $environment;
    private $matcher;
    private $defaultOptions;
    private $translator;

    /**
     * @param \Twig_Environment $environment
     * @param string            $template
     * @param MatcherInterface  $matcher
     * @param array             $defaultOptions
     */
    public function __construct( \Twig_Environment $environment, $template, MatcherInterface $matcher, array $defaultOptions = array() )
    {
        $this->environment = $environment;
        $this->matcher = $matcher;
        $this->defaultOptions = array_merge( array(
            'depth' => null,
            'matchingDepth' => null,
            'currentAsLink' => true,
            'currentClass' => 'current',
            'ancestorClass' => 'current_ancestor',
            'firstClass' => 'first',
            'lastClass' => 'last',
            'template' => $template,
            'compressed' => false,
            'allow_safe_labels' => false,
            'clear_matcher' => true,
            'leaf_class' => null,
            'branch_class' => null
                ), $defaultOptions );
    }

    public function render( ItemInterface $item, array $options = array() )
    {
        $options = array_merge( $this->defaultOptions, $options );
        if( empty( $options['depth'] ) )
        {
            $options['depth'] = PHP_INT_MAX;
        }

        $this->translator = $options['translator'];

        $itemIterator = new \Knp\Menu\Iterator\RecursiveItemIterator( $item );

        $iterator = new \RecursiveIteratorIterator( $itemIterator, \RecursiveIteratorIterator::SELF_FIRST );

        $tree = [];
        $parent = null;
        $levelParent[0] = null;

        $lastLevel = null;
        foreach( $iterator as $item )
        {
            $translatedLabel = $this->translator->trans( $item->getLabel() );
            $id = strtolower( $item->getName() );
            $level = $item->getLevel();
            if( $level <= $options['depth'] )
            {
                $node = [];
                $node['id'] = $id;
                $node['name'] = $translatedLabel;
                $node['uri'] = $item->getUri();
                $node['has_children'] = $item->hasChildren();
                $node['level'] = $level;
                if( $lastLevel !== null )
                {
                    if( $level > $lastLevel )
                    {
                        $parent = $levelParent[$level] = $lastNode['id'];
                    }
                    else
                    {
                        if( $level < $lastLevel )
                        {
                            $parent = $levelParent[$level];
                        }
                    }
                    $lastParent = $parent;
                }
                $node['parent'] = $parent;
                $tree[$id] = $node;
                $lastLevel = $level;
                $lastNode = $node;
            }
        }
        return $tree;
    }

}

menu.js

menu.js requests the menu data and renders it on the page.

define([
    "dojo/dom",
    "dojo/request/xhr",
    "dojo/store/Memory",
    "dijit/tree/ObjectStoreModel",
    "dijit/Tree",
    "dojo/store/JsonRest",
    "dijit/form/ComboBox",
    "dojo/i18n!app/nls/core",
    "dojo/domReady!"
], function (dom,
        xhr, Memory, ObjectStoreModel, Tree, JsonRest, ComboBox, core) {
//"use strict";
    function run() {

        var searchInput, searchStore;

        xhr.get("/api/menustore/adminmenus/", {
            handleAs: "json"
        }).then(function (res) {
            var i, l, store = [], memory, model;
            l = res.length;
            for( i = 0; i < l; i++ ) {
                store.push(res[i]);
            }
            memory = new Memory({
                data: store,
                getChildren: function (object) {
                    return this.query({parent: object.id});
                }
            });

            // Create the model
            var model = new ObjectStoreModel({
                store: memory,
                query: {id: 'admin'},
                mayHaveChildren: function(object) {
                    return typeof object.has_children !== "undefined" && object.has_children;
                }
            });

            // Create the Tree.
            var tree = new Tree({
                id: "admin-menu",
                model: model,
                persist: true,
                showRoot: false,
                onClick: function (item) {
                    if( typeof item.uri !== "undefined" && item.uri !== null ) {
                        location.href = item.uri;
                    }
                },
                getIconClass: function (item, opened) {
                    return (item && item.has_children) ? (opened ? "dijitFolderOpened" : "dijitFolderClosed") : "dijitLeaf"
                }
            }, "admin-left-menu");
            tree.startup();
        });

        searchStore = new JsonRest({
            target: '/api/store/search',
            useRangeHeaders: false,
            idProperty: 'id'});
        searchInput = new ComboBox({
            trim: true,
            "class": "search",
            store: searchStore,
            searchAttr: "name",
            placeholder: core.search
        }, "search");
        searchInput.startup();
        searchInput.on("change", function (evt) {
            console.log(evt);
            console.log(searchInput.store);
        });

    }
    return {
        run: run
    };
});
//# sourceURL=menu.js

Symfony doctrine:query:dql

When developing application that use databases, it is often helpful to run SQL standalone to get the information you need, then integrate it into your code.

With Symfony and Doctrine, it is nice to use DQL because it is a nice bridge between the database and the entities.

You can run the commands, using DQL on the command line with the doctrine:query:dql command, like so:


php bin/console doctrine:query:dql "SELECT p.firstname,p.lastname FROM AppBundle\Entity\Common\Person p WHERE p.id=125"

Bingo Game Maker

This is a very simple way to create bingo boards with random layouts.

Granted you need PHP and Linux, but if you have them, and you want to create bingo boards, you're all set.

The way it works is you create a text file with one square per line. You need at least 25 lines to fill the board because I didn't feel like making any FREE squares.

Run it through bingo.php on the command line like so:

php bingo.php

Once you're happy with it, you can run it a bunch of times using:

source bingo.sh

bingo.php


<?php

$header = <<< HEADER




Bingo



Thanksgiving Bingo


Mark each square as the event occurs.

First person to get all four corners, a complete row across, or a diagonal row gets a prize.

HEADER; $footer = <<< FOOTER
FOOTER; $squares = explode(PHP_EOL,trim(file_get_contents('bingowords.txt'))); shuffle($squares); echo $header.PHP_EOL; echo ''.PHP_EOL; for ($j = 0; $j < 25; $j++) { if (($j % 5) === 0) { echo ''.PHP_EOL; } echo ''.PHP_EOL; if (($j % 5) === 4) { echo ''.PHP_EOL; } } echo '
'.htmlentities($squares[$j]).'
'.PHP_EOL; echo $footer.PHP_EOL;

bingo.sh


#!/bin/bash

rm out.html
for i in `seq 1 25`;
do
php bingo.php >> out.html
done