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.
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.
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.
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).
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).
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.
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.
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 listphpunit
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 therequire
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 theOreilly\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 hypotheticalOreilly\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.
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.