Chapter 4. Components

Modern PHP is less about monolithic frameworks and more about composing solutions from specialized and interoperable components. When I build a new PHP application, rarely do I reach straight for Laravel or Symfony. Instead, I think about which existing PHP components I can combine to solve my problem.

Why Use Components?

Modern PHP components are a new concept to many PHP programmers. I had no idea about PHP components until a few years ago. Before I knew better, I instinctually started PHP applications with a massive framework like Symfony or CodeIgniter without considering other options. I invested in a single framework’s closed ecosystem and used the tools it provided. If the framework did not provide what I needed, I was out of luck and I built additional functionality on my own. It was also difficult to integrate custom or third-party libraries into larger frameworks because they did not share common interfaces. I am relieved to inform you that times have changed, and we are no longer beholden to monolithic frameworks and their walled gardens.

Today, we choose from a vast and continually growing collection of specialized components to create custom applications. Why waste time coding an HTTP request and response library when the guzzlehttp/guzzle component already exists? Why create a new router when the aura/router and league/route components work great? Why spend time coding an adapter to Amazon’s S3 online storage service when the aws/aws-sdk-php and league/flysystem components can be used instead? You get my drift. Other developers have spent countless development hours creating, perfecting, and testing specialized components that do one thing really well. It’s silly not to take advantage of these components to build better applications more quickly instead of wasting time reinventing the wheel.

What Are Components?

A component is a bundle of code that helps solve a specific problem in your PHP application. For example, if your PHP application sends and receives HTTP requests, there’s a component to do that. If your PHP application parses comma-delimited data, there’s a PHP component to do that. If your PHP application needs a way to log messages, there’s a component for that. Instead of rebuilding already-solved functionality, we use PHP components and spend more time solving our project’s larger objectives.

Note

Technically speaking, a PHP component is a collection of related classes, interfaces, and traits that solve a single problem. A component’s classes, interfaces, and traits usually live beneath a common namespace.

In any marketplace, there are good products and there are bad products. The same concept applies to PHP components. Just as you inspect an apple at the grocery store, you can use a few tricks to spot a good PHP component. Here are a few characteristics of good PHP components:

Laser-focused

A PHP component is laser-focused and exists only to solve a single problem very well. It is not a jack-of-all-trades and master of none; it is a master of one. It is obsessed with solving a single problem, and it encapsulates its genius beneath a simple user interface.

Small

A PHP component is no larger than it needs to be. It contains the least amount of PHP code necessary to solve one problem. The amount of code varies. A PHP component can have one PHP class. It can also have several PHP classes organized into subnamespaces. There is no correct number of classes in a PHP component. It uses however many are necessary to solve its one problem.

Cooperative

A PHP component plays well with others. After all, this is the point of PHP components—their existence depends on their cooperation with other components to build larger solutions. A PHP component does not pollute the global namespace with its own code. Instead, a PHP component lives beneath its own namespace to avoid name collisions with other components.

Well-tested

A PHP component is well-tested. This is easy to accomplish thanks to its small size. If a PHP component is small and laser-focused, it is very likely easily tested. Its concerns are few, and its dependencies can be easily identified and mocked. The best PHP components provide their own tests and have sufficient test coverage.

Well-documented

A PHP component is well-documented. It should be easy for developers to install, understand, and use. Good documentation makes this possible. The PHP component should have a README file that says what the component does, how to install it, and how to use it. The component may also have its own website with more in-depth information. And good documentation should also extend into the PHP component’s source code. Its classes, methods, and properties should have inline docblocks that describe the code, its parameters, its return values, and its potential exceptions.

Components Versus Frameworks

The problem with frameworks (particularly older frameworks) is that they are an expensive investment. When we choose a framework, we invest in that framework’s tools. Frameworks usually provide a smorgasbord of tools. But sometimes we need a specific something that the framework does not provide, and it becomes our burden to find and integrate a custom PHP library. Integrating third-party code into a framework is difficult because the third-party code and the PHP framework probably don’t share common interfaces.

When we choose a framework, we invest in that framework’s future. We put our faith behind the framework’s core development team. We assume the framework’s developers will continue investing their own time into developing the framework and ensuring that its code remains up-to-date with modern standards. And often this does not happen. Frameworks are very large, and they require a lot of time and effort to maintain. Project maintainers have their own lives, jobs, and interests. And lives, jobs, and interests change.

Also, who’s to say that a particular framework will remain the best tool for the job? Large projects that exist for many years must perform well and be well-tuned now and into the future. The wrong PHP framework may hinder this ability. Older PHP frameworks that have fallen out of fashion may become slower and outmoded as they lose community support. Older frameworks are often written with procedural code instead of modern object-oriented code. Your newer team members may be unfamiliar with an older framework’s codebase. There is a lot to consider when deciding whether or not to use a PHP framework.

Not All Frameworks Are Bad

So far I’ve spoken only about the downsides of frameworks. Frameworks are not all bad. Symfony is an excellent example of a modern PHP framework. Fabien Potencier and Sensio Labs built the Symfony Framework as an amalgam of smaller and decoupled Symfony components. These components can be used together as a framework or piecemeal in custom applications.

Other, older frameworks are making a similar transition to modern PHP components. The Drupal content management framework is another example. Drupal 7 is written with procedural PHP code that lives in the global PHP namespace. It ignores modern PHP practices to support its legacy codebase. However, Drupal 8 is a ginormous and commendable leap into modern PHP. Drupal 8 leverages the comparative advantages of many different PHP components to build a modern content management platform.

Laravel is also a popular PHP framework written by Taylor Otwell. Like Symfony, Laravel is built atop its own Illuminate component library. However (at time of publishing), Laravel does not adhere to the Semantic Versioning scheme. Don’t let this dissuade you though. Laravel is an amazing framework that can create very powerful applications.

Tip

The most popular modern PHP frameworks include:

Use the Right Tool for the Job

Should you use components or a framework? Use the right tool for the job. Most modern PHP frameworks are only a set of conventions built atop smaller PHP components.

If you are working on a smaller project that can be solved with a precise collection of PHP components, then use components. Components make it super-easy to shop for and use existing tools so we can focus less on boilerplate and more on the larger task at hand. Components also help our code remain lightweight and nimble. We use only the code we need, and it’s super-easy to swap one component with another that may be better suited for our project.

If you are working on a large project with multiple team members and can benefit from the conventions, discipline, and structure provided by a framework, then use a framework. However, frameworks make many decisions for us and require us to adhere to its set of conventions. Frameworks are less flexible, but we do get far more out-of-the-box than we do with a collection of PHP components. If these tradeoffs are acceptable, by all means use a framework to guide and expedite your project development.

Find Components

You can find modern PHP components on Packagist (Figure 4-1), the de facto PHP component directory. This website aggregates PHP components and makes them searchable by keyword. The best PHP components are listed on Packagist. I tip my hat to Jordi Boggiano and Igor Wiedler for creating such an invaluable community resource.

Tip

I’m often asked which components I believe are the best PHP components. This is a subjective question. However, I largely agree with the PHP components listed at Awesome PHP. This is a list of good PHP components curated by Jamie York.

Packagist website
Figure 4-1. Packagist website

Shop

Do not waste your time solving problems that are already solved. Do you need to send or receive HTTP messages? Go to Packagist and search for http; Guzzle is the first result. Use it. Do you need to parse a CSV file? Go to Packagist and search for csv; pick a CSV component and use it. Think of Packagist as a grocery store for PHP components where you can shop for the best ingredients. Packagist probably has a PHP component that solves your problem.

Choose

What if there are multiple PHP components on Packagist that do what you need? How do you pick the best one? Packagist keeps statistics about each PHP component. Packagist tells you how many times each PHP component has been downloaded and starred (Figure 4-2). More downloads and stars indicate a component may be a good option (this is not always true). That being said, don’t discount newer packages with fewer downloads. Many new components are added every day.

It can be difficult to find the perfect PHP component if your Packagist keyword search returns a large number of results. You can’t always rely on download statistics, because crowds are not always right. This is a problem that Packagist must address. I recommend you rely on word of mouth, peer recommendations, and your own experience to confirm your PHP component selection.

Packagist search results
Figure 4-2. Packagist website search results

Leave Feedback

If you find a PHP component that you like, star the PHP component on Packagist and share it with your fellow PHP developers on Twitter, Facebook, IRC, Slack, and other communication networks. This helps the best PHP components bubble up so they are discovered by other developers.

Use PHP Components

Packagist is where you find PHP components. Composer is how you install PHP components. Composer is a dependency manager for PHP components that runs on the command line. You tell Composer which PHP components you need, and Composer downloads and autoloads the components into your project. Because Composer is a dependency manager, it also resolves and downloads your components’ dependencies (and their dependencies, ad infinitum).

Composer works hand-in-hand with Packagist, too. When you tell Composer you want to use the guzzlehttp/guzzle component, Composer fetches the guzzlehttp/guzzle component listing on Packagist, finds the component’s repository URL, determines the appropriate version to use, and discovers the component’s dependencies. Composer then downloads the guzzlehttp/guzzle component and its dependencies into your project.

Composer is important because dependency management and autoloading are hard problems. Autoloading is the process of automatically loading PHP classes on-demand without explicitly loading them with the require(), require_once(), include(), or include_once() functions. Older PHP versions let us write custom autoloaders with the __autoload() function; this function is automatically invoked by the PHP interpreter when we instantiate a class that has not already been loaded. PHP later introduced the more flexible spl_autoload_register() function in its SPL library. Exactly how a PHP class is autoloaded is entirely up to the developer. Unfortunately, the lack of a common autoloader standard often necessitates a unique autoloader implementation for every project. This makes it difficult to use code created and shared by other developers if each developer provides a unique autoloader.

The PHP Framework Interop Group recognized this problem and created the PSR-0 standard (superseded by the PSR-4 standard). The PSR-0 and PSR-4 standards suggest how to organize code into namespaces and filesystem directories so it is compatible with one standard autoloader implementation. As I alluded to in Chapter 3, we don’t have to write a PSR-4 autoloader on our own. Instead, the Composer dependency manager automatically generates a PSR-compatible autoloader for all of our project’s PHP components. Composer effectively abstracts away dependency management and autoloading.

Note

I believe Composer is the most important addition to the PHP community, period. It changed the way I create PHP applications. I use Composer for every PHP project because it drastically simplifies integrating and using third-party PHP components in my applications. If you haven’t used Composer yet, you should start researching Composer today.

How to Install Composer

Composer is easy to install. Open a terminal and execute this command:

curl -sS https://getcomposer.org/installer | php

This command downloads the Composer installer script with curl, executes the installer script with php, and creates a composer.phar file in the current working directory. The composer.phar file is the Composer binary.

Warning

Never execute code that you blindly download from a remote URL. Be sure you review the remote code first so you know exactly what it will do. Also make sure you download the remote code over HTTPS.

I prefer to move and rename the downloaded Composer binary to /usr/local/bin/composer with this command:

sudo mv composer.phar /usr/local/bin/composer

Be sure you run this command to make the composer binary executable:

sudo chmod +x /usr/local/bin/composer

Finally, add the /usr/local/bin directory to your environment PATH by appending this line to your ~/.bash_profile file:

PATH=/usr/local/bin:$PATH

You should now be able to execute composer in your terminal application to see a list of Composer options (Figure 4-3).

Composer options
Figure 4-3. Composer command-line options

How to Use Composer

Now that Composer is installed, let’s download some PHP components. Composer is typically used to download PHP components on a per-project basis.

Component names

First, you should make a list of the components you need for your project. Specifically, note each component’s vendor and package names. Each PHP component has a vendor name and a package name. For example, the popular league/flysystem component’s vendor name is league and its package name is flysystem. The vendor and package names are separated with a / character. Together, the vendor and package names form the full component name league/flysystem.

The vendor name is globally unique and provides the global identity to which its encompassed packages belong. The package name uniquely identifies a single package beneath a given vendor name. Composer and Packagist use the vendor/package naming convention to avoid name collisions among PHP components from different vendors. You can find a PHP component’s vendor and package names on the component’s Packagist directory listing (Figure 4-4).

Packagist vendor and package names
Figure 4-4. Packagist vendor and package name

Component installation

Each PHP component can have many available versions (e.g., 1.0.0, 1.5.0, or 2.15.0). All available versions are listed on the component’s Packagist directory listing.

Semantic Versioning

Modern PHP components use the Semantic Versioning scheme and contain three numbers separated with a period (.) character (e.g., 1.13.2). The first number is the major release number; the major release number is incremented whenever the PHP component is updated with changes that break backward compatibility. The second number is the minor release number; the minor release number is incremented whenever the PHP component is updated with new features that do not break backward compatibility. The third and final number is the patch release number; the patch release number is incremented when the PHP component receives backward-compatible bug fixes.

Fortunately, we don’t have to figure out each component’s most stable version number. Composer does this for us. Navigate to your project’s topmost directory in your terminal application and run this command once for each PHP component:

composer require vendor/package

Replace vendor/package with the component’s vendor and package names. To install the Flysystem component, for example, run this command:

composer require league/flysystem

This command instructs Composer to find and install the PHP component’s most stable version. It also instructs Composer to update the component up to, but not including, the component’s next major version. The previous example, as of January 2016, installs Flysystem version 1.0.16, and it will update the Flysystem component up to, but not including, version 2.0.

You can review the result of this command in the newly created or updated composer.json file in your project’s topmost directory. This command also creates a composer.lock file. Commit both of these files into your version control system.

Example Project

Let’s reinforce our Composer skills by building an example PHP application that scans URLs from a CSV file and reports all inaccessible URLs. Our project will send an HTTP request to each URL. If a URL returns an HTTP response with a status code greater than or equal to 400, we’ll send the inaccessible URL to standard out. Our project will be a command-line application, and the path to the CSV file will be the first and only command-line argument. Ultimately, we’ll execute our script, pass it the CSV file path, and see a list of inaccessible URLs on standard out:

php scan.php /path/to/urls.csv

Our project directory looks like Figure 4-5.

Scanner application filesystem
Figure 4-5. Component directory structure

The first thing I do when starting a new PHP project is determine what tasks can be solved with existing PHP components. The scan.php script opens and iterates a CSV file, so we’ll need a PHP component that can read and iterate CSV data. The scan.php script also sends an HTTP request to each URL in the CSV file, so we’ll need a PHP component that can send HTTP requests and inspect HTTP responses. It is certainly possible to write our own code to iterate a CSV file or send HTTP requests, but why should we waste our time if these problems are already solved? Remember, our goal is to scan a list of URLs. Our job is not to build HTTP and CSV parser libraries.

After browsing Packagist, I find the guzzlehttp/guzzle and league/csv PHP components. The former handles HTTP messages and the latter parses and iterates CSV data. Let’s install these components with Composer using these commands in the project’s topmost directory:

composer require guzzlehttp/guzzle;
composer require league/csv;

These commands instruct Composer to download these two components into a new vendor/ directory in the project’s topmost directory. It also creates a composer.json file and a composer.lock file.

The composer.lock file

After you install project dependencies with Composer, you’ll notice that Composer creates a composer.lock file. This file lists all of the PHP components used by our project and the components’ exact version numbers (including major, minor, and patch numbers). This effectively locks our project to these specific PHP component versions.

Why is this important? If a composer.lock file is present, Composer downloads the specific PHP component versions listed in the composer.lock file regardless of the component’s latest available version on Packagist. You should version control the composer.lock file and distribute it to your team members so they can use the same PHP component versions as you. If your team members, your staging server, and your production server all use the same PHP component versions, you minimize the risk of bugs caused by component version discrepancies.

The one downside with the composer.lock file is that composer install will not install versions newer than those listed in the composer.lock file. If you do need to download newer component versions and update your composer.lock file, use composer update. The composer update command updates your components to their latest stable versions and also updates the composer.lock file with new PHP component version numbers.

Autoloading PHP components

Now that our project’s PHP components are installed with Composer, how do we use them? Luckily for us, when Composer downloads the PHP components it also creates a single PSR-compatible autoloader for all of our project dependencies. All we have to do is require Composer’s autoloader at the top of the scan.php file:

<?php
require 'vendor/autoload.php';

Composer’s autoloader is just a PHP file named autoload.php located inside the vendor/ directory. When Composer downloads each PHP component, Composer inspects each component’s own composer.json file to determine how the component prefers to be autoloaded and, with this information, creates a local PSR-compatible autoloader for it. Ultimately, we can instantiate any of our project’s PHP components and they are autoloaded on-demand! Pretty neat, huh?

Implement scan.php

Let’s finish the scan.php script using the Guzzle and CSV components. Remember, the path to the CSV file is provided as the first command-line argument (accessible in the $argv array) when our PHP script is executed. The scan.php script looks like Example 4-1.

Example 4-1. URL scanner app
<?php
// 1. Use Composer autoloader
require 'vendor/autoload.php';

// 2. Instantiate Guzzle HTTP client
$client = new \GuzzleHttp\Client();

// 3. Open and iterate CSV
$csv = \League\Csv\Reader::createFromPath($argv[1]);
foreach ($csv as $csvRow) {
    try {
        // 4. Send HTTP OPTIONS request
        $httpResponse = $client->options($csvRow[0]);

        // 5. Inspect HTTP response status code
        if ($httpResponse->getStatusCode() >= 400) {
            throw new \Exception();
        }
    } catch (\Exception $e) {
        // 6. Send bad URLs to standard out
        echo $csvRow[0] . PHP_EOL;
    }
}
Tip

Pay attention to how we use the \League\Csv and \GuzzleHttp namespaces when we instantiate the guzzlehttp/guzzle and league/csv components. How do we know to use these particular namespaces? I read the guzzlehttp/guzzle and league/csv documentation. Remember, good PHP components have documentation.

Add a few URLs to the urls.csv file, one URL per line. Make sure at least one URL is invalid. Next, open a terminal and execute the scan.php script:

php scan.php urls.csv

We execute the php binary and pass it two arguments. The first argument is the path to the scan.php script. The second argument is the path to the CSV file that contains a list of URLs. If any of the URLs return an unsuccessful HTTP response, they are output to the terminal screen.

Command-Line Scripts with PHP

Did you know you can write command-line scripts with PHP? This is a great way to automate maintenance tasks for your web application. Learn more about writing PHP command line scripts here:

Composer and Private Repositories

So far I’ve assumed you are using open source PHP components that are publicly available. As much as I create and use open source software, I recognize that using only open source PHP components may not always be possible. Sometimes we have to mix open source and proprietary components in the same application. This is especially true for companies that use internally developed PHP components that cannot be open sourced due to licensing or security concerns. Composer makes this a nonissue.

Composer can manage private PHP components whose repositories require authentication. When you run composer install or composer update, Composer prompts you if a component’s repository requires authentication credentials. Composer also asks if you want to save the repository authentication credentials in a local auth.json file (created adjacent to the composer.json file). An example auth.json file looks like this:

{
    "http-basic": {
        "example.org": {
            "username": "your-username",
            "password": "your-password"
        }
    }
}

In most cases, you should not version control the auth.json file. Instead, let project developers create their own auth.json file with their own authentication credentials.

If you’d rather not wait for Composer to request authentication credentials, you can manually tell Composer your authentication credentials for a remote machine with this command:

composer config http-basic.example.org your-username your-password

In this example, http-basic lets Composer know we are adding authentication details for a given domain. The example.org hostname identifies the remote machine that contains the private component repository. The final two arguments are the username and password credentials. By default, this command saves credentials in the current project’s auth.json file.

You can also save authentication credentials system-wide by using the --global flag. This flag lets Composer use your credentials for all projects on your local machine:

composer config --global http-basic.example.org your-username your-password

Global credentials are saved in the ~/.composer/auth.json file. If you are using Windows, global credentials are saved in %APPDATA%/Composer.

Tip

Learn more about Composer and private repositories in Authentication management in Composer.

Create PHP Components

By this point you should be able to find and use PHP components. Let’s switch gears and talk about creating PHP components. Specifically, we’ll convert the URL scanner application into a PHP component and submit it to the Packagist component directory.

Creating PHP components is a great way to share your work with the greater PHP community. The PHP community is built on a foundation of sharing and helping others. If you use open source components in your applications, it’s always nice to return the favor with a new and innovative open source component.

Tip

Be careful that you do not rewrite components that already exist. If you improve upon an existing component, consider sending your improvements to the original component as a pull request. Otherwise, you risk confusing and fragmenting the PHP component ecosystem with duplicate components.

Vendor and Package Names

Before I build a PHP component, I choose the component’s vendor and package name. Remember, each PHP component uses a globally unique vendor and package name combination to avoid name collisions with other components. I recommend you use only lowercase letters for your vendor and package names.

A vendor name is the brand or identity to which a component belongs. Many of my own PHP components use the codeguy vendor name because this is my online identity. Choose a vendor name that best represents you or your component’s brand.

Tip

Search Packagist before you choose a vendor name to make sure it is not already claimed by another developer.

A package name identifies a PHP component beneath a given vendor name. Many components can live beneath a single vendor name. For this example, I’ll use modernphp as the vendor name and scanner as the package name.

Namespaces

As we discussed in Chapter 2, each component lives beneath its own PHP namespace so that it does not pollute the global namespace or collide with other components that use the same PHP class names.

A common misconception is that the component’s PHP namespace must match the component’s vendor and package names. This is not true. The component’s PHP namespace is unrelated to the component’s vendor and package names. The vendor and package names are only used by Packagist and Composer to identify a component. You use the component’s namespace when using the component in your PHP code.

For this tutorial, we’ll create our component beneath the PHP namespace Oreilly\ModernPHP. This namespace does not exist yet. I just pulled this out of thin air for this particular component.

Filesystem Organization

PHP components have largely standardized on this filesystem structure:

src/

This directory contains the component’s source code (e.g., PHP class files).

tests/

This directory contains the component’s tests. We will not use this directory in this example.

composer.json

This is the Composer configuration file. This file describes the component and tells Composer’s autoloader to map your component’s PSR-4 namespace to the src/ directory.

README.md

This Markdown file provides helpful information about this component, including its name, description, author, usage, contributor guidelines, software license, and credits.

CONTRIBUTING.md

This Markdown file describes how others can contribute to this component.

LICENSE

This plain-text file contains the component’s software license.

CHANGELOG.md

This Markdown file lists changes introduced in each new component version.

Tip

If you’re having trouble starting your own PHP component, have a look at the PHP League’s excellent PHP component boilerplate repository.

The composer.json File

The composer.json file is required and must contain valid JSON. It includes information used by Composer to find, install, and autoload the PHP component. It also contains information for the component’s Packagist directory listing.

Example 4-2 shows a composer.json file for our URL scanner component. It includes all of the composer.json properties that I use most often for my own PHP components.

Example 4-2. The URL Scanner component composer.json file
{
    "name": "modernphp/scanner",
    "description": "Scan URLs from a CSV file and report inaccessible URLs",
    "keywords": ["url", "scanner", "csv"],
    "homepage": "http://example.com",
    "license": "MIT",
    "authors": [
        {
            "name": "Josh Lockhart",
            "homepage": "https://github.com/codeguy",
            "role": "Developer"
        }
    ],
    "support": {
        "email": "help@example.com"
    },
    "require": {
        "php" : ">=5.4.0",
        "guzzlehttp/guzzle": "^6.1"
    },
    "require-dev": {
        "phpunit/phpunit": "^5.0"
    },
    "suggest": {
        "league/csv": "^8.0"
    },
    "autoload": {
        "psr-4": {
            "Oreilly\\ModernPHP\\": "src/"
        }
    }
}

This is admittedly a lot to digest, so let’s step through each composer.json property in detail:

name

This is the component’s vendor and package name, separated with a / character. This value is displayed on Packagist.

description

This contains a few sentences that succinctly describe the component. This description is displayed on Packagist.

keywords

This contains an appropriate number of keywords that describe the component. These keywords help others find this component on Packagist.

homepage

This is the URL of the component’s website.

license

This is the software license with which the PHP component is released. I prefer to use the MIT Public License. You can read more about software licenses at http://choosealicense.com. Remember to always release your code with a license.

authors

This is an array of information for each project author. You should include at least a name and URL for each author.

support

This is how the component’s users find technical support. I prefer to include an email address and support forum URL. You could also list an IRC channel, for example.

require

This lists the PHP component’s own component dependencies. You should list each dependency’s vendor/package name and minimum version number. I also like to list the minimum PHP version required by this component. All dependencies listed beneath this property are installed for both development and production project installations.

require-dev

This acts like the require property, but it lists only the dependencies required to develop this component. For example, I often list phpunit as a dev dependency so that other component contributors can write and run tests. These dependencies are installed only during development. They are not installed in production projects.

suggest

This acts like the require property, but it merely suggests other components because they may be useful when used with our component. Unlike the require property, this object’s values are free text fields that describe each suggested component. Composer does not install suggested components.

autoload

This tells the Composer autoloader how to autoload this component. I recommend you use the PSR-4 autoloader, as demonstrated in Example 4-2. Beneath the psr-4 property, you map the component’s namespace prefix to a filesystem path relative to the component’s root directory. This makes our component compatible with a standard PSR-4 autoloader. In Example 4-2, I map the Oreilly\ModernPHP namespace to the src/ directory. The mapping’s namespace must end with two back slash characters (\\) to avoid conflicts with other components that use a namespace with a similar sequence of characters. Based on the example mapping, if we instantiate a hypothetical Oreilly\ModernPHP\Url\Scanner class, Composer will autoload the PHP class file at src/Url/Scanner.php.

Tip

Learn more about the complete composer.json schema at getcomposer.org.

The README file

The README file is often the component’s first introduction to its users. This is especially true for components hosted on GitHub and Bitbucket. Therefore, it’s important that the component’s README file provides, at a minimum, this information:

  • Component name and description

  • Install instructions

  • Usage instructions

  • Testing instructions

  • Contributing instructions

  • Support resources

  • Author credits

  • Software license

Tip

GitHub and Bitbucket can render README files in Markdown format. This means you can write well-formatted README files with headers, lists, links, and images. Use this to your advantage! All you have to do is add the .md or .markdown file extension to the README file. The same principle applies to the CONTRIBUTING and CHANGELOG files. Learn more about the Markdown format at Daring Fireball.

Component Implementation

And now we arrive at the component’s meat and potatoes—its implementation. This is where you write the PHP classes, interfaces, and traits that form the PHP component. What classes you write, and how many, depends entirely on the PHP component’s purpose. However, all component classes, interfaces, and traits must live in the src/ directory and exist beneath the component’s namespace prefix listed in the composer.json file.

For this demonstration, I’ll create a single PHP class named Scanner that exists beneath the Url subnamespace beneath the Oreilly\ModernPHP namespace listed in the composer.json file. The Scanner class file lives at src/Url/Scanner.php. The Scanner class implements the same logic as our earlier URL scanner example application, except it encapsulates the URL scanning behavior in a PHP class (Example 4-3).

Example 4-3. The URL Scanner component class
<?php
namespace Oreilly\ModernPHP\Url;

class Scanner
{
    /**
     * @var array An array of URLs
     */
    protected $urls;

    /**
     * @var \GuzzleHttp\Client
     */
    protected $httpClient;

    /**
     * Constructor
     * @param array $urls An array of URLs to scan
     */
    public function __construct(array $urls)
    {
        $this->urls = $urls;
        $this->httpClient = new \GuzzleHttp\Client();
    }

    /**
     * Get invalid URLs
     * @return array
     */
    public function getInvalidUrls()
    {
        $invalidUrls = [];
        foreach ($this->urls as $url) {
            try {
                $statusCode = $this->getStatusCodeForUrl($url);
            } catch (\Exception $e) {
                $statusCode = 500;
            }

            if ($statusCode >= 400) {
                array_push($invalidUrls, [
                    'url' => $url,
                    'status' => $statusCode
                ]);
            }
        }

        return $invalidUrls;
    }

    /**
     * Get HTTP status code for URL
     * @param string $url The remote URL
     * @return int The HTTP status code
     */
    protected function getStatusCodeForUrl($url)
    {
        $httpResponse = $this->httpClient->options($url);

        return $httpResponse->getStatusCode();
    }
}

Instead of parsing and iterating a CSV file, we inject an array of URLs into the Scanner class constructor. We want our URL scanner class to be as generic as possible. If we demand a CSV file, we inherently limit our component’s usefulness. If we accept an array of URLs, we let the end user decide how to fetch an array of URLs (from a PHP array, a CSV file, an iterator, etc). That being said, we still recommend the league/csv component because it can be helpful for developers using our component. We include the league/csv component in the composer.json manifest’s suggest property.

The Scanner class has a hard dependency on the guzzlehttp/guzzle component. However, we isolate each URL’s HTTP request in the getStatusCodeForUrl() method. This lets us stub (or override) this method’s implementation in our component’s unit tests so that our tests do not rely on a working Internet connection.

Version Control

We’re almost done. Before we submit our component to Packagist, we must publish it to a public code repository. I prefer to publish my open source PHP components to GitHub. However, any public Git repository is fine (I have published this component to GitHub).

It’s also a good idea to tag each component release using the Semantic Versioning scheme. This lets component consumers request specific versions of your component (e.g., ^1.2). I’ll create a 1.0.0 tag for the URL scanner component.

Packagist Submission

Now we’re ready to submit the component to Packagist. If you don’t use GitHub, go ahead and create a Packagist account. You can also log in to Packagist with your GitHub credentials.

Once logged in, click the big green Submit Package button at the top right of the website. Enter the full Git repository URL into the Repository URL text field and click the Check button. Packagist verifies the repository URL and prompts you to confirm your submission. Click Submit to finalize your component submission. Packagist creates and redirects you to the component listing, which looks Figure 4-6.

Packagist listing
Figure 4-6. Packagist component listing

You’ll notice it pulls the component name, description, keywords, dependencies, and suggestions from the component’s composer.json file. You’ll also notice that it shows the repository branches and tags, too. Packagist establishes a direct correlation between repository tags and semantic version numbers. This is why I recommend your repository tags be valid version numbers like 1.0.0, 1.1.0, and so on. However, we still have that big red alert message that reads:

This package is not auto-updated. Please set up the GitHub Service Hook for
Packagist so that it gets updated whenever you push!

We can activate a GitHub or Bitbucket hook that notifies Packagist whenever the component repository is updated. Learn how to setup this repository hook at https://packagist.org/profile/.

Using the Component

We’re done! Now anyone can install the URL scanner component with Composer and use it in their PHP applications. Run this command in your terminal to install the URL scanner component with Composer:

composer require modernphp/scanner

Then you can use the URL scanner component, as shown in Example 4-4.

Example 4-4. URL Scanner component usage
<?php
require 'vendor/autoload.php';

$urls = [
    'http://www.apple.com',
    'http://php.net',
    'http://sdfssdwerw.org'
];
$scanner = new \Oreilly\ModernPHP\Url\Scanner($urls);
print_r($scanner->getInvalidUrls());

Get Modern PHP now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.