Variable number of parameters on a prepared statement in PHP

Sometimes, you need to use a different number of parameters for a prepared statement. The call_user_func_array function allows you to call bind_param with a varying number of parameters.



// Start by validating your data
$something = filter_input(INPUT_POST, 'something', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^(this|that|other|thing)$/i']]);

// Set up the types string
$types = '';

// Initialize the array the variables will be referenced from
$values = [];

// Create the base SQL statement
$query = 'SELECT * FROM table';

// Initialize an array to store the WHERE comparisons
$where = [];

// Check to see if $something should be used 
// empty tests for both null (no data in input) and false (invalid data)
if (!empty($something)) {
   
    // Set the WHERE comparison for something
    $where[] = 'something = ?';

    // Append the type for this comparison
    $types .= 's';

    // Add a reference to the $something variable (that's what the ampersand is)
    $values[] = &$something;

}

// If the $where array has elements, add them to the query
if (count($where) > 0) {
    $query .= ' WHERE '.implode(' AND ',$where);
}

$stmt = $mysqli->prepare($query);

// Create the array of parameters which will be sent to the bind_param method
$params = array_merge([$types],$values);

// Bind the variables
call_user_func_array([$stmt,'bind_param'],$params);

What Branch am I on Anyway?

Often during web development the code is switching between different branches and it can be difficult to let everyone on a small team know which branch is in use.

Adding this line to a PHP script and using CSS to make it easy to see can save time.

<div class="git-branch">'.`git name-rev --name-only HEAD`.'</div>

Be sure to wrap it in some code to prevent display on the production site.

One Approach to Complying with a "script-src 'self'" Content Security Policy

I recently encountered this error when working with plugin code on an application:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' chrome-extension-resource:".

The cause of the error was inline script code I was using to pass values from the server to the client.

After a bit of research (see the link above), the best solution looked like a little bit of PHP code to create the JavaScript required to pass the values to the client.

The overhead of checking the timestamp and creating the file is minimal, so this code recreates the JavaScript once each day.

<?php

Class CSP {
	const JSFILENAME = 'csp.js';

	static public function cspFilename($dir = __DIR__) {
		return $dir.'/'.self::JSFILENAME;
	}

	static public function cspFileNeedsRebuild($filename) {
		if (!is_file($filename)) {
			return true;
		}
		$fileLastModified = date('z',filemtime($filename));
		$today = date('z');
		return $fileLastModified !== $today;
	}
}

$someValue = 'Some value';
$jsFilename = CSP::cspFilename();
if (CSP::cspFileNeedsRebuild($jsFilename)) {
	$js = 'var someValue = "'.$someValue.'";'.PHP_EOL;
	file_put_contents($jsFilename,$js);
}
echo '<script src="'.$jsFilename.'"></script>'; 

Other solutions I could have used would have been to disable the Content Security Policy, but that's really a stupid approach. There is also nonce and one may code the policy with more complex values.

Presenting KnpMenus with Dojo

This Dojo module will traverse a menu delivered by the KnpMenuBundle and present it as a Dijitized menu bar.

There may be a few extraneous modules included, but this code will likely be extended.

define([
    "dojo/_base/declare",
    "dojo/dom",
    "dojo/dom-attr",
    "dojo/dom-construct",
    "dojo/on",
    "dojo/query",
    "dijit/registry",
    "dijit/MenuBar",
    "dijit/MenuBarItem",
    "dijit/PopupMenuItem",
    "dijit/PopupMenuBarItem",
    "dijit/MenuItem",
    "dijit/DropDownMenu",
    "dijit/form/Button",
    "dijit/Dialog",
    "app/lib/common",
    "dojo/i18n!app/nls/core",
    "dojo/NodeList-traverse",
    "dojo/domReady!"
], function (declare, dom, domAttr, domConstruct, on, query, registry,
        MenuBar, MenuBarItem, PopupMenuItem, PopupMenuBarItem, MenuItem, DropDownMenu, Dialog,
        lib, libGrid, core) {

    function run() {
        var menuBar = new MenuBar({}, "admin-top-menu");

        function createMenuItem(widget, parent, depth) {
            var children, node, item, i, label, nextNode;
            var popupMenuObj, labelObj, link;
            children = query(parent).children();
            for( i = 0; i < children.length; i++ ) {
                node = children[i];
                nextNode = (typeof children[i + 1] !== "undefined") ? children[i + 1] : null;
                switch( node.tagName ) {
                    case "SPAN":
                    case "A":
                        label = node.textContent.trim();
                        if( typeof node.href !== "undefined") {
                            link = node.href;
                        } else {
                            link = null;
                        }
                        if( nextNode !== null && nextNode.tagName === "UL" ) {
                            popup = new DropDownMenu();
                            popupMenuObj = {label: label, popup: popup};
                            if( depth <= 1 ) {
                                item = new PopupMenuBarItem(popupMenuObj);
                            } else {
                                item = new PopupMenuItem(popupMenuObj);
                            }
                            createMenuItem(popup, nextNode);
                        } else {
                            labelObj = {label: label};
                            if( depth <= 1 ) {
                                item = new MenuBarItem(labelObj);
                            } else {
                                item = new MenuItem(labelObj);
                            }
                        }
                        if( link !== null ) {
                            item.on("click", function () {
                                location.href = link
                            });
                        }
                        widget.addChild(item);
                        break;
                    case "LI":
                        createMenuItem(widget, node, depth + 1);
                        break;
                }
            }
        }

        var menuElements = query("#admin-top-menu ul");
        if( menuElements.length > 0 ) {
            createMenuItem(menuBar, menuElements[0], 0);
            domConstruct.destroy(menuElements[0]);
        }
        menuBar.startup();
    }
    return {
        run: run
    };
});

Symfony / Dojo - Prod and Dev environment management

Dojo has a great build process which allows you to create a optimized and minimized files for the client side (and more!).

In a production environment, this greatly improves performance.

However, building the code after every change will slow development significantly.

Since Dojo can load the required modules dynamically, you can load the source files and work with them directly, and maintain your profile file as you work. Running a build at the end of each development session will help to ensure the code and profile stay in sync.

admin.base.html.twig

This template provides the foundation page layout for all admin pages. If the application is running in a dev environment, it includes a page_footer_script, but in production, it includes dojo.js

{% if app.environment == 'dev' %}
    {% include 'admin/parts/page_footer_script.html.twig' %}
{% else %}
    <script data-dojo-config="async:1" src="/release/dojo/dojo.js"></script>
{% endif %}

{% block javascripts %}
{% endblock %}

{% if omit_menu is not defined %}
    <script>
        require([
            "app/admin/menu",
            "dojo/domReady!"
        ], function (menu) {
            menu.run();
        });
    </script>
{% endif %}

page_footer_script.html.twig

Used only in the dev environment


<script>
    var dojoConfig = {
        async: true,
        baseUrl: '/release',
        paths: {"lib": "/app/lib",
            "nls": "/app/nls"},
        packages: [
            {"name": 'app', "location": '/app'},
            'dojo',
            'dijit',
            'dojox',
            'dgrid',
            'dstore',
            'put-selector',
            'xstyle'
        ],
        selectorEngine: 'lite',
        tlmSiblingOfDojo: false,
        has: {
            "dojo-trace-api": false
        }
    };
</script>
<script data-dojo-config="async:1" src="/vendor/dojo/dojo/dojo.js"></script>

example-page.html.twig

Each page template has a javascripts block which includes the require call to bring in the client side code.


{% block javascripts %}
    <script>
        require(["app/admin/asset/brand"], function (brand) {
            brand.run();
        });
    </script>
{% endblock %}

Symfony / Dojo - Prod and Dev environment management