RequireJS and Magento2

How they work together and, if you want, how to turn it off.

Magento2 uses RequireJS to load its javascript. The library is made to be a browser-based solution to a classic problem in frontend development: how to incorporate third-party dependencies in your javascript. Rather than having to place a page’s dependencies in script tags in each page’s header, RequireJS handles everything for you asynchronously, allowing your javascript to define its own dependencies, and allowing a progressive-enhancement-oriented workflow.

More of a visual learner? Check out this article in YouTube video form: RequireJS and Magento2!

Anatomy of x-magento-init

If you do a simple view source of any Magento2 shop’s page, you may see some weird-looking code like this:

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "wishlist": {
                        "component": "Magento_Wishlist/js/view/wishlist"
                    }
                }
            }
        }
    }
</script>

That’s how Magento2 defines its component and page-level javascript dependencies. Those javascript dependencies, in turn, have dependencies on other scripts (that RequireJS also loads), which are defined across your application’s code base and stitched together (and cached) by Magento2 at run time.

By default, every Magento page brings in three javascript files:

  1. requirejs/require.js – The base RequireJS script
  2. requirejs/requirejs-config.js – The Magento2 RequireJS config script
  3. requirejs/mixins.js (see also, Alan Storm’s article on Magento2 mixins)

We’re going to be focusing on the first two. When the page loads, RequireJS loads the configuration defined in the requirejs-config.js file, which bootstraps the page and parses blocks of JSON like we found above.

So what does it do with these blocks of JSON? Step with me through the code!

Step 1: Oh hello, there. Let’s load up some Javascript!

<script type="text/x-magento-init">
</script>

When Magento’s bootstrapping script runs across any script with the attribute type="text/x-magento-init", it knows that therein is a blob of JSON that points to an element and a component that RequireJS is going to have to handle.

Step 2: Let’s look for an element, shall we? Or not.

<script type="text/x-magento-init">
    {
        "*": {

        }
    }
</script>

In this step, the front end developer tells Magento’s script resolver to only continue with this component dependency if the element matching the selector (in the case above, *, so it will always match) is found on the page.

Note: if that element is not found on the current page, Magento won’t even go searching for the component’s javascript at all. RequireJS won’t inject the script tag into the header, and the client will never see it.

Moreover, when you’re creating custom components, Magento will pass the element(s) matching that selector into the component for you to play with. Pretty cool, huh?

Step 3: Scripts away!

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                
            }
        }
    }
</script>

We’ve made it this far, which means it’s time to call up some javascript! In this block, the text to the left of the colon tells RequireJS that it needs to load scripts from that location. On my local development environment, this means that RequireJS throws the following script into the header:

<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="Magento_Ui/js/core/app" src="http://127.0.0.1:8080/static/version1491571223/frontend/Magento/blank/en_US/Magento_Ui/js/core/app.js"></script>

Then, the client loads that script (asynchronously), and it executes. Typically, these files will return a function, however, that passes in the element we discussed above, as well as the configuration…

Step 4: All together now…

<script type="text/x-magento-init">
    {
        "*": {
            "Magento_Ui/js/core/app": {
                "components": {
                    "wishlist": {
                        "component": "Magento_Wishlist/js/view/wishlist"
                    }
                }
            }
        }
    }
</script>

If the script that RequireJS just loaded up is a component (using the define function whose second argument is a function returning a function), RequireJS will execute that returned function and pass it two arguments. The first argument is the JSON blob, as a variable, nested under the Magento_Ui/js/core/app node. (Confused? Don’t worry. We will cover this more in a future article.)

The second, is the element (a DOM node) or elements found with the initial selector. In this case, since we’re using the global selector (*), it actually just passes in false as the second argument.

This way, you can load a page and fully render it via Magento2’s PHP templating engine, yet still have dynamic and fully configured/configurable javascript embedded in the page. However, the client never has to execute any javascript within a <script> tag.

Pretty nifty!

RequireJS Dependencies

These isolated components won’t be able to do much by themselves. That’s where another feature of RequireJS comes into play: dependency management and injection.

If you inspect any Magento2 component, you will find that they look something like this:

define([
    'jquery',
    'underscore'
], function ($, _) {
    'use strict';

    // Setup stuff

    return function (config, element) {
        // Actual component-level stuff here
    };
});

As you may be able to tell, the define function simply defines a component’s dependencies before defining the component’s behavior. In this example, the two dependencies are jquery and underscore, which are passed into the actual component initialization function as two arguments (here, $ for jquery, and _ for underscore.js).

How does RequireJS know where to find dependencies?

Dependencies are mapped via the requirejs/requirejs-config.js that we mentioned above! To see the lot of them, view the the configuration file. Dependency mappings will look something like:

'ko': 'knockoutjs/knockout',
'knockout': 'knockoutjs/knockout',
'mageUtils': 'mage/utils/main',
'rjsResolver': 'mage/requirejs/resolver'

RequireJS will then look for the KnockoutJS dependency in knockoutjs/knockout (which it will later append .js to, after adding the appropriate static asset path to the URL) whenever you request a dependency named either ko or knockout.

Why is this useful?

So why do we do this instead of just using the old-school inline javascript method? There are a few reasons:

1. Inline Javascript is difficult to debug. It’s hard enough tracking down what goes where in Magento without having to constantly regression test bits of functionality when we’re making casual template changes, or trying to modify certain bits of JS code.

2. SECURITY! Magento2 is slowly turning the ship towards a focus on security, and part of that is adding support for tighter content security policies. While Magento has always chosen flexibility over strict security practices, it is absolutely impossible to bring a platform up to date in the fight against cross-site scripting without the ability to do something like the x-magento-init functionality allows. With it, we can move toward disallowing inline javascript from executing, which walls off one of the largest surface areas for XSS attacks on the web.

3. Dependency Management. If you’ve done any work with Magento 1, you have probably come upon an instance where you were trying to inject some form of javascript into your page that uses jQuery. But uh oh! It won’t work at all because jQuery isn’t loaded yet… Doh! So you had to do all kinds of hacks (like a setTimeout) to get it to work. Not anymore. RequireJS waits for all of your component’s dependencies to resolve before invoking the function, so you can be sure that jQuery is loaded.

4. Component Isolation. Let’s face it. The old way (inline scripts, no build process, no dependency management) can lead you to nicely isolated, reusable components. But I’ll bet you didn’t really do it, did you? I like the x-magento-init method of javascript injection because it nudges developers toward better practices like code reuse and component isolation (no more cluttering up the global namespace!). If Mage2 isn’t going to be using the newest, hottest Server-Side Rendered React methods, I’m happy that it’s at least drifting toward better practices.

If you create components using the methods outlined above (e.g., using the define function), you can make your components unit-testable, reusable, and as pure as possible.

Conclusion

Now you know the basics of RequireJS and how Magento2 uses it. Our next article will cover how we can remove the enormous bloat that Mage2 ships with core: or, how to reduce the script size on your default Mage2 home page from 125+ javascripts, down to something more manageable for your project.

A Magento2 Frontend Workflow

Using Grunt, LESS, NPM, and Yarn, you can make quick, iterative changes to your LESS files that LiveReload in the browser.

This short tutorial assumes you have Magento2 installed and ready to go. I’m using a local install on OSX, but these same commands and principles will work if you install Mage2 via Docker or Vagrant, as well.

Note: for a visual dive into this subject, check out the YouTube video I made on the subject, A Magento2 Frontend Workflow.

Magento2 ships with LESS because its easy to use PHP to compile LESS into CSS. When they made that architectural decision, there was no PHP SCSS implementation. Mage2 aims to have as few requirements as possible, however, so they want to ensure you can run Magento on a standard LAMP box.

But you can’t really do quick changes if you need Mage2 to compile your static assets. The php bin/magento setup:static-content:deploy command takes minutes to compile.

So to increase the development speed, Mage2 ships with a Grunt build process for those who want to take advantage of it.

Requirements for the Grunt Tools

  • Node
  • NPM

Make sure your local box has Node installed — when you install it, you will also have NPM.

Step 1: Install Yarn and Grunt

Just use Yarn instead of NPM to manage your dependencies. Seriously. It’s much faster than NPM, and is intended to be a drop-in replacement for it.

npm install -g yarn will install Yarn globally for you via NPM. Then, use Yarn to install Grunt globally via yarn global add grunt.

This will take a few minutes.

Step 2: Install the Mage2 Node Dependencies

In your Magento2 main directory (your-project/magento2) you should see a file named package.json.sample. This is the file that tells node what dependencies you need to run the applications contained therein.

Bring your command line into that directory (e.g., cd your-project/magento2).

Rename the file from package.json.sample to package.json, using a command like mv package.json.sample package.json.

Now, you’re ready to install the node dependencies! Do so by typing yarn in the command line from your your-project/magento2 directory.

Let that run. It will take a few minutes to process, as yarn is working to install and link together node dependencies that it will place in the your-project/magento2/node_modules folder.

Congrats! You’re setup. Test it by running the command yarn grunt clean. If it runs without errors, you’re ready to start.

(Optional) Step 3: Add your custom theme to the Grunt configuration

Off the bat, you should be good to go. But if you’re working with a template that isn’t the default one, you will need to add your theme to the Grunt configuration. In your-project/magento2/dev/tools/grunt/configs/ folder, edit the themes.js file.

This file exports configuration of all of the themes that Grunt is aware of. Add a configuration option for your theme so that the file looks something like:

/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

'use strict';

/**
 * Define Themes
 *
 * area: area, one of (frontend|adminhtml|doc),
 * name: theme name in format Vendor/theme-name,
 * locale: locale,
 * files: [
 * 'css/styles-m',
 * 'css/styles-l'
 * ],
 * dsl: dynamic stylesheet language (less|sass)
 *
 */
module.exports = {

    mytheme: {
        area: 'frontend',
        name: 'YourProject/Theme',
        locale: 'en_US',
        files: [ 'your/theme/CSS' ],
        dsl: 'less'
    },

    blank: {
        area: 'frontend',
        name: 'Magento/blank',
        locale: 'en_US',
        files: [
            'css/styles-m',
            'css/styles-l',
            'css/email',
            'css/email-inline'
        ],
        dsl: 'less'
    },
    luma: {
        area: 'frontend',
        name: 'Magento/luma',
        locale: 'en_US',
        files: [
            'css/styles-m',
            'css/styles-l'
        ],
        dsl: 'less'
    },
    backend: {
        area: 'adminhtml',
        name: 'Magento/backend',
        locale: 'en_US',
        files: [
            'css/styles-old',
            'css/styles'
        ],
        dsl: 'less'
    }
};

Your theme should be in the namespace called YourProject, under the directory your-project/magento2/app/design/frontend/Theme. Modify the above configuration if your template is located elsewhere. For more information on how to create themes, checkout the official Magento2 docs on theming.

Now, you should be able to run any of the commands below with your custom theme specified. E.g., yarn grunt clean:mytheme.

Commands

You now have a set of commands you can use that are quicker to execute and will help you develop your theme far quicker than using Mage2’s PHP CLI tools.

Clean

yarn grunt clean[:your-theme]

Cleans the your-project/magento2/pub/static directory of existing moved and/or symlinked assets that Magento2 will serve on page visits.

Note: you probably won’t need to run this command much. Use the next one instead.

Exec

yarn grunt exec[:your-theme]

Cleans the static directory of existing assets and recreates them based on the current file system.

This command is essential. If ever you add new LESS files, Grunt won’t actually be able to know about them until you re-run this command so that it pulls LESS files from your theme folder into the pub/static directory.

LESS

yarn grunt less[:your-theme]

Compiles the LESS files that Grunt finds in the pub/static directory from the entry points defined in your your-project/magento2/dev/tools/grunt/configs/themes.js directory.

Watch

yarn grunt watch

Watches your pub/static directory for changes in files (which are replicated over the symlinks to your theme directory), then recompiles the LESS files.

In addition, this command runs a LiveReload server, so if you have a browser extension for LiveReload, this command will push new changes live to the browser.

Troubleshooting

LiveReload isn’t working.

It can be a be tricky to get going right. There’s a specific order you need to run the Grunt commands in to get your LiveReload working (somewhat) consistently:


yarn grunt exec && yarn grunt less && yarn grunt watch

If you don’t do it this way, you may forget to compile your LESS files in the pub/static directory, so when you load the page, Mage2 will compile it for you. This will throw off the LiveReloader for some reason. Just stop your yarn grunt watch command and run the above string of commands.

I added a LESS file and @imported it, but it’s not showing up?

Because Grunt creates symlinks to individual files, and not full directories, if you create new LESS files, you have to rerun yarn grunt exec && yarn grunt less && yarn grunt watch (the yarn grunt exec being critical here) to have that file appear in the directory that Grunt watches.

Sidenote: Grunt symlinks individual files instead of directories for a good reason. If it did the full theme directory, Grunt would compile the CSS and spit it back out to your actual theme folder.

How does all of this work?

I could write it up, but instead I made a video. For a deeper explanation of what Grunt is doing here, check out the YouTube video I made on the subject, A Magento2 Frontend Workflow.

When to Use CSS Grids

What they are, and what they are not

CSS grids are amazingly useful. They take normal, single-dimensional grids into a whole other dimension (literally)! But there are some limitations designers and front-end developers need to be aware of before spending a ton of time refactoring to them…

If you haven’t heard about CSS Grids yet, what are you doing? Go read about them, now! Support for them just dropped in the evergreen browsers, save Safari, which is due out in April. That means that by the end of the year, we could see as high as 50-70% support. (I just made this number up.)

But as I’ve been working with a designer on what this means, and how we can best use CSS grids, I’ve come across a few limitations and things to be aware of before fully adopting CSS grids as the Swiss Army knife of layout tools…

Before I do that, though, let’s talk about what CSS Grids are.

CSS Grids

CSS Grids simply add the Y-plane to a container element’s layout awareness. Typically, browsers lay out elements from left to right (or right to left if you’re in a right-to-left locale). With a grid, the developer can set a series of columns and/or rows — known as “tracks” — for a container’s elements to fall into upon render.

Elements can span rows and columns, columns can be fluid, have max- and min-widths, and so on.

The most obvious use for CSS grids is for layout. Other layout methods like flex box rely on single-track, left-to-right rendering, using manual breaks, row dividers, or other methods to tell the browser when there’s a new row.

With CSS grids, you define in the CSS where things should go, independent of markup order, and with the ability to change the dimensions of the grid.

For example, a class two-column, header and footer layout in flex box:

See the Pen Flex Layout, Basic by Ryan Poe (@baublet) on CodePen.

In CSS grids, this becomes:

See the Pen CSS Grid Layout, Basic by Ryan Poe (@baublet) on CodePen.

(Note: note here that we can specify that the track itself can grow to fill the viewport? A nice benefit of CSS Grids.)

Grids have the benefit of letting us move things around. Want to swap your header and footer? Simple:

See the Pen CSS Grid Layout, Basic by Ryan Poe (@baublet) on CodePen.

Want to add a column to the top right of your header — say, a promotional banner? Add a new track and drop your div into your markup:

See the Pen CSS Grid Layout, Basic by Ryan Poe (@baublet) on CodePen.

CSS grids make your basic page layouts work really well, and really simply. You can even use it to do things like layout a grid of known-width and known-height tiles:

See the Pen Grid by Example 6: Line-based placement span keyword by rachelandrew (@rachelandrew) on CodePen.

What Grids Are Not

CSS grids are not design grids

Grids, however, comes with some limitations. By far the biggest limitation, and one that separates CSS grids from its namesake, is the fact that it isn’t a design grid. It’s a layout grid.

What do I mean?

A design grid, as opposed to a CSS grid, overlays a web page or other design document, intending to have the elements in the page align visually along the grid’s various axes. These grids are intended to establish a visual rhythm and consistency to a website. Pages laid out in a grid look more coherent and unified.

An Ebay site with a grid overlaid. Notice the horizontal visual consistency.

However, on the web, laying out content to a grid is very difficult. We can’t use pixels, since we need to design for virtually every display size between 280px and 20,080px. Percentages work well for top-level elements. But on subgrids (grids within grids), there is very little hope of having that grid align with the parent grid without a mess of selectors and edge-case nightmares.

In addition, percentages won’t help us for vertical units — if you want your grid column gaps to be the same as your row gaps, percentages won’t work. (This is because the browser calculates the percentage as part of the parent value. If the width and height are different, the row gaps and column gaps will be different. Indeed, most browsers don’t even know how to calculate the row gap as a percentage, so won’t take the unit.)

I tinkered with a ton of different ways to ensure visual consistency using just blocks and guides to no avail. Subgrids will just never want to align perfectly to a parent grid automatically.

Notice how the grid comprising the three “hello” containers only slightly aligns to the overall grid — and this is my best attempt, abandoning the grid column gaps property.

With some difficulty, you can definitely use a CSS grid to help you enforce a visual consistency, but you will have to use a mix of CSS grids and flex box row/col grids to do it — along with a fair bit of discipline and oversight of your markup and development process.

One notable exception is if you discard column gaps and margins entirely, and opt to control spacing within columns. The problem? This just shoves the problem of aligning items down the pipeline, to elements within columns, rather than columns. And the whole purpose of a grid system is to alleviate spacing issues between individual elements so that every single element doesn’t need to specify its own spacing…

Bottom line: it’s probably not worth your time trying to fit an entire website into a CSS grid apart from basic layout. CSS grids are not meant for this use case. It can help, especially with layouts, but it’s not a panacea.

CSS Grids are not a replacement for flex box

Flex box solves a very specific, but common problem on the web: how do we ensure that a series of block-level elements stay next to each other with consistent, expected behaviors?

Flex box lays out items along a single axis (x or y; horizontal or vertical). It’s very, very good at that. And while grid can do that, too, don’t use grid where you can use flex box instead. For example, use a CSS grid with a flex box fallback for your layout, but sometimes, you just want two columns, side-by-side on desktop; yet horizontal on mobile. Sure, you can do this with a CSS grid and a flex box fallback, but why bother writing all that code and worrying about the support for grids?

See the Pen Flex Panel by Ryan Poe (@baublet) on CodePen.

On the other hand, do you have a grid of products that you know will almost always contain multiple rows? Do these rows contain items that are of a specific, known height and width? Perfect.

See the Pen Product Grid with Flex Box Fallback by Ryan Poe (@baublet) on CodePen.

But note how the flex box fallback isn’t so hot? that’s because…

CSS grids are not a replacement for masonry

Masonry is a way of organizing tiles of known or unknown height and width into a dense grid of items. Think Pinterest, but with even more flexibility. It uses javascript because, at least at the moment, it has to. You can fake a masonry-style grid using the techniques I showed above, but if you don’t know the width and height of your items, they won’t fit in your grid quite right.

Remember, grids are created by defining your tracks in terms rows and columns with specified heights — or, at least, fractions/percentages. Masonry doesn’t really care about tracks, it just pieces together an assortment of odd-sized block-level elements as well as it can using absolute positioning and a series of calculations about the container object and its child elements.

CSS grids doesn’t get us any closer to the browser doing this for us, unfortunately.

Magento 1.x Directory Structure Guide

Because it might not make sense at first, second, third, nth, ... glances.

This past week, I’ve been taking a crash-course in Magento 1.x. I’ve been thoroughly impressed that it runs a fairly sophisticated ORM that implements an ActiveRecord-like API in an otherwise DataMapped pattern. It only calls a record’s resource handler (which saves the data to the database) when you call a row’s save() method. Pretty cool stuff.

But holy moly, what a complex, spaghetti-like system.

It took me forever (nearly 4 solid days) to finally figure out where I could find a module’s code, view files, and configuration. So I decided to share the guide I wrote up on it.

TLDR; Magento makes you put your modules in three primary places:

  • Definition: app/etc/modules/[Your Module].xml
  • Code: app/code/[Code Pool]/[Namespace]/[Module Name]/
  • Views: app/design/[Area]/[Store]/(layout|template)

Magento is a system for building and managing what it calls modules (which, by default, form an e-commerce website). Modules are collections of functionality, blocks, and models that make up your website. Modules live in namespaces, which describe bundles of modules. Namespaces, in turn, all live in code pools, which house collections of namespaces that can be overwritten by other code pools in a linear sequence.

When a module is called, Magento looks for the module in the code pools in the following order. If it can’t find the module in the first code pool, it tries the second, then the third, and so on, throwing an error if it cannot find it.

  1. (Any custom code pools defined in the project)
  2. Local
  3. Community
  4. Core

So where are my modules?!

Modules are spread across the filesystem in Magento.

First, a module is defined at the most basic level in:

app/etc/modules/[Your Module].xml

Magento won’t be able to find your module if you don’t define its configuration. Through configuration here, you can usually figure out where the rest of the module lives.

Cool, so where’s the code?

The actual code for a model (that is, the Controllers, Observers, Methods, Collections, Resources, and so on) is in:

app/code/[Code Pool]/[Namespace]/[Module Name]/

Your code pool will most likely be local, but could be custom ones if set by your configuration. Node that in your module’s namespace, some directories are capitalized, others aren’t. This is because when Magento builds paths with its autoloading function, everything in the scope of the autoloader will begin with a capital letter, everything outside the scope of the autoloader (and typically loaded through Magento internals) begin with lowercase letters.

Common directories and their purposes in your code pool (note the casing):

            Block/        block template classes
            controllers/    controller files
            data/        data to be loaded to the DB
            etc/        module configuration XML files
            Helper/
            Model/        model classes
            Model/Resource/    resources classes for models
            Model/Resource/Collection    collection classes for models
            sql/        module installation and upgrade files

You will likely spend the bulk of your time in this directory.

The view files for your module can live in a number of places, but they are centrally located in:

app/design/[Area]/[Store]/

There are two areas your templates and block definition files live: frontend, and adminhtml. As you may expect, frontend contains files that are user/customer-facing, while adminhtml are files that correspond to behavior in the administration menu.

The store is the store that you define in Magento’s store manager. Each store can have its own templates, and fall back in a linear fashion similar to code pools. The order that Magento searches for templates and blocks is: Store > Default > Base. If it cannot locate the designated layout files in base, it throws a 404.

Your module’s blocks are defined in separate XML files from their templates within your design namespace:

app/design/[Area]/[Store]/layout/[Your Module].xml
                       /template/[Your Module]/

The XML file is the configuration for your blocks that stitch together your block code and templates, and your templates directory contains all of the block’s .phtml file.

You Might Not Need a Testing Framework

It depends (doesn't everything?)

It’s tempting to pull in behemoth testing frameworks even for tiny projects. But you might not need to…

I was recently working on PSR-compliant Dependency Container for when I get around to incorporating dependency injection into my plugin, wp-twig-loop-shortcode (I know, what a name). I wanted a way to easily test it, however, without having to pull in something like PHPUnit for what amounted to three very small classes (and three interfaces).

The release build for PHPUnit is over 400kb. And while a slim framework for the most part, I don’t really need the complicated functionality it provides. The tests I plan to run in it can fit in a small file.

So what to do?

Since PHP 4, the language has carried a native assert method. It allows you to test a boolean as true, throwing an exception if it’s false. In test.php, which we run via php test.php,

assert(true);
assert(false);

Will give you

Warning: assert(): Assertion failed in /home/ubuntu/workspace/DependencyContainer/Test/test.php on line 4

Call Stack:
    0.0003     248360   1. {main}() /home/ubuntu/workspace/DependencyContainer/Test/test.php:0
    0.0003     248456   2. assert() /home/ubuntu/workspace/DependencyContainer/Test/test.php:4

Ultimately, these are the only assertions you need to unit test simple classes and methods. Testing frameworks are made for complicated projects and full applications where constantly writing assertions so that they pass boolean tests becomes a huge hassle. Testing frameworks with a handful of assertions keeps your code cleaner and easier to read.

Additionally, if your project has multiple tests, a testing framework may come in handy for its ability to run many tests in one sweep.

For my purposes, I was able to fully test my dependency container in less than 100 lines of code:

require_once("../DependencyContainer.php");

$container = new DependencyContainer();

$global_scope_checker = true;

$container->inject("test", function($arguments)
{
    return  [
        "depends" =>
          $arguments["sammy"] . $arguments[0] . $arguments[1]()  
        ];
    },
    [   "sammy" => "zane",
        3,
        function() { return 42; }
    ]
);

$container->inject("test2", function($args)
{
    global $global_scope_checker;
    $global_scope_checker = false;
    return true;
});


echo "Beginning test for DependencyContainer\n\n";

assert(!$container->has("test"));
echo ".";

$dep = $container->get("test");
assert(is_array($dep));
echo ".";

assert(count($dep) == 1);
echo ".";

assert("zane342" == $dep["depends"]);
echo ".";

/**
 * This test ensures that dependencies aren't initialized until they're called,
 * to ensure that we can setup a bunch of dependencies up front, but not necessarily
 * need to load them all into memory if our request doesn't call for it.
 */

assert($global_scope_checker);
echo ".";

$dep = $container->get("test2");
assert(!$global_scope_checker);
echo ".";

assert($dep);
echo ".";

/**
 * This test ensures that the proper NotFoundExceptions are thrown
 */
try {
    $container->get("not-here");
} catch(Exception $ex) {
    assert(is_a($ex, "Exception"));
    echo ".";
    assert(is_a($ex, "DependencyContainerNotFoundException"));
    echo ".";
}

/**
 * This test ensures that exceptions are thrown properly when our injectors throw
 * errors.
 */
$container->inject("fails", function() {
    throw new Exception("nothing here");
});
try {
    $container->get("fails");
} catch (Exception $ex) {
    assert(is_a($ex, "Exception"));
    echo ".";
    assert(is_a($ex, "DependencyContainerException"));
    echo ".";
}

echo "\n\nTest suite complete! All tests pass.\n";

This returns very simple output:

$ php test.php
Beginning test for DependencyContainer

...........

Test suite complete! All tests pass.

Simple, easy to see, and doesn’t require including a testing framework for such a small project.

Continuous Integration

This method can also be used in continuous integration services like Travis CI! It takes minimal configuration.

In your .travis.yml file:

language: php
php:
  - '5.4'
  - '5.5'
  - '5.6'
  - '7.0'
script: cd Test && php test.php

My class doesn’t support PHP versions below 5.4 because prior to that, the language did not support anonymous functions. PHP 5.3 supported anonymous functions, but this implementation also required arrays that could be assigned to functions. That functionality was not added until PHP 5.4.

Now, add your Travis config file to your repo, and all tests should pass! Congrats. Now your simple module can stay tested as you make changes to it without Travis CI having to load complicated testing suites and test runners.

In Summary

Use testing frameworks if you need complicated assertions or you need to run multiple tests. For simple projects that can achieve over 100% test coverage with a few assertions, such as my DependencyContainer project, try using PHP’s built-in assert() function.