MD\Foundation is a Swiss Army Knife for PHP

I'm releasing a set of useful PHP classes that ease development and abstract away some very common code.

v

How many times have you caught yourself reinventing the wheel or writing the same simple code over and over again when you had to get values found under a property in a collection of objects or arrays?

As programmers we often dabble in low level functions and algorithms instead of creating business logic that pushes our projects towards success. These low level methods are very much needed, but have already been solved numerous times and there isn't really any need to create them by hand everytime we stumble upon a case which could benefit from it.

Here comes MD\Foundation:

MD\Foundation is a set of useful PHP classes and functions that ease your development and abstract away some very common code.

It is what Lo-Dash is for JavaScript.

Visit MD\Foundation Docs

A lot of the methods in MD\Foundation you have probably already seen elsewhere, implemented in a lib or a framework, or potentially you have already created some of them on your own. The goal of this library is not to discover anything new or innovate on existing concepts. Instead, it just solves one thing - gives access to fully tested and well documented set of everyday tools that deal with common tasks and can simplify your PHP code.

This library is a result of my 10+ years of experience in PHP, where in various projects I needed various functions and had to copy them around. Then GitHub came, and then Composer came and life became much easier. Single place to store all those utils was needed and thus MD\Foundation was born. So even though the first commit is dated on April 25th, 2013, this project is way way older. All of its funcionalities have been created due to "real world needs" and have been tested in production environments.

Utils and helpers

The most important part of MD\Foundation is the Utils namespace. You will find various useful methods in there that deal with strings, arrays and objects.

The list of methods in Utils classes is quite extensive and they are grouped into classes by the type of input they deal with.

Some notable methods in StringUtils class are:

echo \MD\Foundation\Utils\StringUtils::urlFriendly('Lorem & ipsum dolor sit amet!');
// -> 'lorem-and-ipsum-dolor-sit-amet'
echo \MD\Foundation\Utils\StringUtils::secondsToTimeString(3785);
// -> '1:03:05'
echo \MD\Foundation\Utils\StringUtils::hexToRgb('#B8860B');
// -> array(184, 134, 11)

echo \MD\Foundation\Utils\StringUtils::rgbToHex(array(255, 182, 193));
// -> 'ffb6c1'

echo \MD\Foundation\Utils\StringUtils::rgbToHex('75, 0, 130');
// -> '4b0082'

echo \MD\Foundation\Utils\StringUtils::rgbToHex('189:183:107', ':');
// -> 'bdb76b'

The most "feature rich" class ArrayUtils also has a lot to offer and it shares a lot of methods with ObjectUtils. Most of them will help you in dealings with collections of data (e.g. database results or feed data):

echo \MD\Foundation\Utils\ArrayUtils::pluck(array(
    array('id' => 4, 'title' => 'Lipsum', 'author' => 'John'),
    array('id' => 3, 'title' => 'Lorem ipsum', 'author' => 'Jane'),
    array('id' => 2, 'title' => 'Dolor sit amet', 'author' => 'Doe')
), 'title');
// -> array('Lipsum', 'Lorem ipsum', 'Dolor sit amet')

In addition, the ObjectUtils class provides two helper methods ::getter() and ::setter() to dynamically build names of getters and setters for objects - even if properties aren't named using camelCase.

ObjectUtils also internally tries to access any object properties, by first trying if getter methods exist (so that it receives any modifications you make to the properties in their getter methods) and then checking if such property is public and accessible.

Extended glob()

One feature that I really miss in PHP's glob() implementation is globstar that was introduced in Bash 4. Globstar basically allows to easily traverse directories and subdirectories in search of files. A pattern like js/**/*.js means that you will get a lit of all *.js files inside any directories and their subdirectories (and subsubdirectories, and so on) found in js/ directory.

With FilesystemUtils class you can now use globstar feature in PHP.

It's worth noting that if you want to find files inside current directory and their subdirectories, then you have to use a GLOB_BRACE flag and pattern, e.g.:

\MD\Foundation\Utils\FilesystemUtils::glob('{,**/}*.js', GLOB_BRACE)
// -> array(
//      'main.js',
//      'dir/script.js',
//      'dir/more/scripts.js'
// );

Implementation of this convention varies between libs in various languages and I decided to stick with what Bash manual states. More about this is explained in #2.

Basic cryptography

If for any reasons you are not yet running PHP 5.5+, then you're missing out on important security features like password_hash(). For years now applying simple md5() or even sha1() on your users' passwords hasn't been enough to protect them from bad people, and even salting turns out to be not so hack-proof. There is a great article on password security that explains why md5(sha1(md5($password . 'sdipfunwpi4t'))) is not so secure and why you shouldn't reinvent the wheel on a problem that has already been solved. You should read it now, no excuses.

So why the Hasher class? Because unfortunately the code presented in that article is not OOP and is not available via Composer. My class is almost direct copy of the given code only rewritten to a class that can be instantiated and configured via the constructor to be used as a service in your application.

Encrypting messages

While I was implementing the hashing algorithms into MD\Foundation, I have also needed a simple cipher class, to easily encrypt and decrypt some data. Therefore a Cipher was created.

$cipher = new \MD\Foundation\Crypto\Cipher('thisismysecret');
$message = $cipher->encrypt('Hide from NSA... not really...');
echo $message;
// -> 'LEm/lBP+IVPlLvKQbUOlTkNAG34EwXH+mwqp4QCdOxQ='

echo $cipher->decrypt($message);
// -> 'Hide from NSA... not really...'

Throw me some exceptions

I love throwing exceptions. I used to hate throwing exceptions. I used to think that every error should be suppressed and attempted to recover from. This led to applications that didn't work and nobody knew why. So now when I throw exceptions, I'm making sure that if an application doesn't work then at least someone will have a better guess and figuring out why.

Simply throwing an Exception is not enough, though, you should be more specific than that (preferably much more specific). PHP already comes with a set of predefined exceptions, but on various occassions I needed some more.

Inbetween InvalidFileException, NotFoundException or NotUniqueException I went a little bit over the top and implemented two "special" exceptions that help generate meaningful messages: InvalidArgumentException and NotImplementedException.

It is sometimes tedious to write full message describing invalid argument and what was given, so with MD\Foundation you can ease your life with this:

function myFunction($str) {
    if (empty($str)) {
        throw new \MD\Foundation\Exceptions\InvalidArgumentException('non-empty string', $str);
    }
}

try {
    myFunction('');
} catch(\InvalidArgumentException $e) {
    echo $e->getMessage();
    // -> myFunction expected argument 1 to be non-empty string, string ("") given.
}

And when you are developing new classes and want to quickly mock up your architecture and come back to actual implementation later (and writing @todo's all over the place doesn't really help), you can throw NotImplementedException to get back to it whenever is necessary. It will automagically build a message that will help you spot missing implementations in your logs:

function doSomething() {
    throw new \MD\Foundation\Exceptions\NotImplementedException();
}

doSomething()
// -> Uncaught exception: Function "doSomething" defined in "example.php" has not been fully implemented yet.

Runtime debugging

Sometimes it is needed to reason about code, classes or data structures during runtime. Whatever the reason, the Debugger class can help you with this. For example it can be a little bit more specific on variable type then PHP's gettype() function - ::getType() will return actual class name instead of just object for objects; or it can read class hierarchy from a class name with ::getObjectAncestors().

But my real cherry in the Debugger is ::consoleDump() method (with a shortcut alias \MD\console_log()) which will take whatever number of arguments and dump them in the browser's console. Yup. No need to pollute user experience while you're debugging in production.

There is also a Timer for benchmarking your code.

Log level hierarchy for Psr\Log

One feature missing for me in PSR-3 and \Psr\Log implementation is log levels hierarchy. When you have different loggers in your application, you may want to filter the messages that they log based on their level. A common practice, but Psr\Log doesn't implement any tool to do this and doesn't even specify the hierarchy of levels.

So I went ahead and created LogLevels - a simple static class that does just that. It evaluates PSR log levels to the same values as Monolog (which follows RFC 5424).

function log($level, $message, array $context) {
    if (\MD\Foundation\LogLevels::isHigherLevel($level, \Psr\Log\LogLevel::CRITICAL)) {
        send_sms($message, $context);
    }
}

Contributions welcome

So this is it, for an overview of my MD\Foundation library. Head over to www.michaldudek.pl/Foundation for full documentation and list of all features.

You can get MD\Foundation with Composer:

$ composer require michaldudek/foundation dev-master

And you can also head over to GitHub to check the code or open any issues. Maybe even a Pull Request? :)

I'm open to any contributions you might have. Be it opinions, critique or pull requests - all are welcome.

As you might have notice, as of day of this writing, MD\Foundation isn't yet tagged with 1.0 version, so it's not a final release yet. For now, I will treat it as a Release Candidate or maybe even a beta - to check how people like it, if there's anything that should be added or anything that should be changed. The methods and API's have gone through several iterations already and I don't think they will be changing anymore, so you can freely use it in your projects.

I hope you like it!

comments powered by Disqus