PHP & Web Development Blogs

Search Results For: introduction
Showing 1 to 4 of 4 blog articles.
7328 views · 1 years ago

![Introduction to Gitlab CI for PHP developers](https://images.ctfassets.net/vzl5fkwyme3u/5EUoVwcn2inEG3LsNJFAYp/14e5c704d91665c0de6ffd506a283ec3/AdobeStock_90389954.png?w=1000)

As a developer, you've probably at least heard something about [CI - Continuous integration](https://en.wikipedia.org/wiki/Continuous_integration). And if you haven't - you better fix it ASAP, because that's something awesome to have on your skill list and can get extremely helpful in your everyday work. This post will focus on CI for PHP devs, and specifically, on CI implementation from [Gitlab](https://docs.gitlab.com/ee/ci/README.html). I will suppose you know the basics of [Git](https://git-scm.com/), [PHP](https://php.net/), [PHPUnit](https://phpunit.de/), [Docker](https://www.docker.com/) and unix shell. Intended audience - intermediate PHP devs.

Adding something to your workflow must serve a purpose. In this case the goal is to automate routine tasks and achieve better quality control. Even a basic PHP project IMO needs the following:

* [linter](https://en.wikipedia.org/wiki/Lint_(software)) checks (cannot merge changes that are invalid on the syntax level)

* Code style checks

* Unit and integration tests

All of those can be just run eventually, of course. But I prefer an automated CI approach even in my personal projects because it leads to a higher level of discipline, you simply can't avoid following a set of rules that you've developed. Also, it reduces a risk of releasing a bug or regression, thus improving quality.

Gitlab is as generous as giving you their CI for free, even for your private repos. At this point it is starting to look as advertising, therefore a quick comparison table for [Gitlab](https://about.gitlab.com/pricing/), Github, [Bitbucket](https://bitbucket.org/product/pricing). AFAIK, Github does not have a built-in solution, instead it is easily integrated with third parties, of which [Travis CI](https://github.com/marketplace/travis-ci/plan/MDIyOk1hcmtldHBsYWNlTGlzdGluZ1BsYW43MA==#pricing-and-setup) seems to be the most popular - I will therefore mention Travis here.

### Public repositories (OSS projects). All 3 providers have a free offer for the open-source community!

| Provider | Limits |

|---|---|

| Gitlab | 2,000 CI pipeline minutes per group per month, shared runners |

| Travis | Apparently unlimited |

| Bitbucket| 50 min/month, max 5 users, File storage <= 1Gb/month |

### Private repositories

| Provider | Price | Limits |

|---|---|---|

| Gitlab | Free | 2,000 CI pipeline minutes per group per month, shared runners |

| Travis | $69/month | Unlimited builds, 1 job at a time |

| Bitbucket| Free | 50 min/month, max 5 users, File storage <= 1Gb/month |

## Getting started

I made a small project based on Laravel framework and called it "ci-showcase". I work in Linux environment, and the commands I use in the examples, are for linux shell. They should be pretty much the same on Mac and nearly the same on Windows though.

```sh

composer create-project laravel/laravel ci-showcase

```

Next, I went to gitlab website and created a new public project: https://gitlab.com/crocodile2u/ci-showcase. Cloned the repo and copied all files and folders from the newly created project - the the new git repo. In the root folder, I placed a `.gitignore` file:

```

.idea

vendor

.env

```

Then the `.env` file:

```

APP_ENV=development

```

Then I generated the application encryption key: `php artisan key:generate`, and then I wanted to verify that the primary setup works as expected: `./vendor/bin/phpunit`, which produced the output `OK (2 tests, 2 assertions)`. Nice, time to commit this: `git commit && git push`

[At this point](https://gitlab.com/crocodile2u/ci-showcase/tree/step-1), we don't yet have any CI, let's do something about it!

### Adding .gitlab-ci.yml

Everyone going to implement CI with Gitlab, is strongly encouraged to bookmark this page: https://docs.gitlab.com/ee/ci/README.html. I will simply provide a short introduction course here plus a bit of boilerplate code to get you started easier.

First QA check that we're going to add is PHP syntax check. PHP has a built-in linter, which you can invoke like this: `php -l my-file.php`. This is what we're going to use. Because the `php -l` command doesn't support multiple files as arguments, I've written a small wrapper shell script and saved it to `ci/linter.sh`:

```sh

#!/bin/sh

files=`sh ci/get-changed-php-files.sh | xargs`

last_status=0

status=0

# Loop through changed PHP files and run php -l on each

for f in "$files" ; do message=`php -l $f` last_status="$?" if [ "$last_status" -ne "0" ]; then # Anything fails -> the whole thing fails echo "PHP Linter is not happy about $f: $message" status="$last_status" fi

done

if [ "$status" -ne "0" ]; then echo "PHP syntax validation failed!"

fi

exit $status

```

Most of the time, you don't actually want to check each and every PHP file that you have. Instead, it's better to check only those files that have been changed. The Gitlab pipeline runs on every push to the repository, and there is a way to know which PHP files have been changed. Here's a simple script, meet `ci/get-changed-php-files.sh`:

```sh

#!/bin/sh

# What's happening here?

#

# 1. We get names and statuses of files that differ in current branch from their state in origin/master.

# These come in form (multiline)

# 2. The output from git diff is filtered by unix grep utility, we only need files with names ending in .php

# 3. One more filter: filter *out* (grep -v) all lines starting with R or D.

# D means "deleted", R means "renamed"

# 4. The filtered status-name list is passed on to awk command, which is instructed to take only the 2nd part

# of every line, thus just the filename

git diff --name-status origin/master | grep '\.php$' | grep -v "^[RD]" | awk '{ print $2 }'

```

These scripts can easily be tested in your local environment ( at least if you have a Linux machine, that is ;-) ).

Now, as we have our first check, we'll finally create our `.gitlab-ci.yml`. This is where your pipeline is declared using [YAML notation](https://yaml.org/):

```yml

# we're using this beautiful tool for our pipeline: https://github.com/jakzal/phpqa

image: jakzal/phpqa:alpine

# For this sample pipeline, we'll only have 1 stage, in real-world you would like to also add at least "deploy"

stages: - QA

linter:

stage: QA

# this is the main part: what is actually executed

script: - sh ci/get-changed-php-files.sh | xargs sh ci/linter.sh

```

The first line is `image: jakzal/phpqa:alpine` and it's telling Gitlab that we want to run our pipeline using a PHP-QA utility by [jakzal](https://github.com/jakzal). It is a docker image containing PHP and a huge variety of QA-tools. We declare one stage - QA, and this stage by now has just a single job named `linter`. Every job can have it's own docker image, but we don't need that for the purpose of this tutorial. Our project reaches [Step 2](https://gitlab.com/crocodile2u/ci-showcase/tree/step-2). Once I had pushed these changes, I immediately went to the [project's CI/CD page](https://gitlab.com/crocodile2u/ci-showcase/pipelines). Aaaand.... the pipeline was already running! I clicked on the `linter` job and saw the following happy green output:

```

Running with gitlab-runner 11.9.0-rc2 (227934c0) on docker-auto-scale ed2dce3a

Using Docker executor with image jakzal/phpqa:alpine ...

Pulling docker image jakzal/phpqa:alpine ...

Using docker image sha256:12bab06185e59387a4bf9f6054e0de9e0d5394ef6400718332c272be8956218f for jakzal/phpqa:alpine ...

Running on runner-ed2dce3a-project-11318734-concurrent-0 via runner-ed2dce3a-srm-1552606379-07370f92...

Initialized empty Git repository in /builds/crocodile2u/ci-showcase/.git/

Fetching changes...

Created fresh repository.

From https://gitlab.com/crocodile2u/ci-showcase * [new branch] master -> origin/master * [new branch] step-1 -> origin/step-1 * [new branch] step-2 -> origin/step-2

Checking out 1651a4e3 as step-2...

Skipping Git submodules setup

$ sh ci/get-changed-php-files.sh | xargs sh ci/linter.sh

Job succeeded

```

It means that our pipeline was successfully created and run!

### PHP Code Sniffer.

[PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) is a tool for keeping app of your PHP files in one uniform code style. It has a hell of customizations and settings, but here we will only perform simple check for compatibilty with [PSR-2](https://www.php-fig.org/psr/psr-2/) standard. A good practice is to create a configuration XML file in your project. I will put it in the root folder. Code sniffer can use a few file names, of which I prefer `phpcs.xml`:

```xml

<?xml version="1.0"?>

/resources

```

I also will append another section to `.gitlab-ci.yml`:

```yml

code-style: stage: QA script: # Variable $files will contain the list of PHP files that have changes - files=`sh ci/get-changed-php-files.sh` # If this list is not empty, we execute the phpcs command on all of them - if [ ! -z "$files" ]; then echo $files | xargs phpcs; fi

```

Again, we check only those PHP files that differ from master branch, and pass their names to `phpcs` utility. That's it, [Step 3](https://gitlab.com/crocodile2u/ci-showcase/tree/step-3) is finished! If you go to see the pipeline now, you will notice that `linter` and `code-style` jobs run in parallel.

## Adding PHPUnit

Unit and integration tests are essential for a successful and maintaiable modern software project. In PHP world, [PHPUnit](https://phpunit.de/) is de facto standard for these purposes. The PHPQA docker image already has PHPUnit, but that's not enough. Our project is based on [Laravel](https://laravel.com/), which means it depends on a bunch of third-party libraries, Laravel itself being one of them. Those are installed into `vendor` folder with [composer](https://getcomposer.org/). You might have noticed that our `.gitignore` file has `vendor` folder as one of it entries, which means that it is not managed by the Version Control System. Some prefer their dependencies to be part of their Git repository, I prefer to have only the `composer.json` declarations in Git. Makes the repo much much smaller than the other way round, also makes it easy to avoid bloating your production builds with libraries only needed for development.

Composer is also included into PHPQA docker image, and we can enrich our `.gitlab-ci.yml`:

```yml

test: stage: QA cache: key: dependencies-including-dev paths: - vendor/ script: - composer install - ./vendor/bin/phpunit

```

PHPUnit requires some configuration, but in the very beginning we used `composer create-project` to create our project boilerplate. **laravel/laravel** package has a lot of things included in it, and `phpunit.xml` is also one of them. All I had to do was to add another line to it:

```xml

```

APP_KEY enironment variable is essential for Laravel to run, so I generated a key with `php artisan key:generate`.

`git commit` & `git push`, and we have all three jobs on the **QA** stage!

## Checking that our checks work

In [this branch](https://gitlab.com/crocodile2u/ci-showcase/tree/failing-checks) I intentionally added changes that should fail all three job in our pipeline, take a look at [git diff](https://gitlab.com/crocodile2u/ci-showcase/compare/step-4...failing-checks). And we have this out from the pipeline stages:

**Linter**:

```

$ ci/linter.sh

PHP Linter is not happy about app/User.php:

Parse error: syntax error, unexpected 'syntax' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST) in app/User.php on line 11

Errors parsing app/User.php

PHP syntax validation failed!

ERROR: Job failed: exit code 255

```

**Code-style**:

```

$ if [ ! -z "$files" ]; then echo $files | xargs phpcs; fi

FILE: ...ilds/crocodile2u/ci-showcase/app/Http/Controllers/Controller.php

----------------------------------------------------------------------

FOUND 0 ERRORS AND 1 WARNING AFFECTING 1 LINE

---------------------------------------------------------------------- 13 | WARNING | Line exceeds 120 characters; contains 129 characters

----------------------------------------------------------------------

Time: 39ms; Memory: 6MB

ERROR: Job failed: exit code 123

```

**test**:

```

$ ./vendor/bin/phpunit

PHPUnit 7.5.6 by Sebastian Bergmann and contributors.

F. 2 / 2 (100%)

Time: 102 ms, Memory: 14.00 MB

There was 1 failure:

1) Tests\Unit\ExampleTest::testBasicTest

This test is now failing

Failed asserting that false is true.

/builds/crocodile2u/ci-showcase/tests/Unit/ExampleTest.php:17

FAILURES!

Tests: 2, Assertions: 2, Failures: 1.

ERROR: Job failed: exit code 1

```

Congratulations, our pipeline is running, and we now have much less chance of messing up the result of our work.

## Conclusion

Now you know how to set up a basic QA pipeline for your PHP project. There's still a lot to learn. Pipeline is a powerful tool. For instance, it can make deployments to different environments for you. Or it can build docker images, store artifacts and more! Sounds cool? Then spend 5 minutes of your time and leave a comment, you can also tell me if there is a pipeline topic you would like to be covered in next posts.

5948 views · 1 years ago

![PHP IPC with Daemon Service using Message Queues, Shared Memory and Semaphores](https://images.ctfassets.net/vzl5fkwyme3u/4ULcw2rCysGcSGOAi2uKOk/450013591b84069c5536663430536714/AdobeStock_200383770.jpeg?w=1000)

# Introduction

In a previous article we learned about [Creating a PHP Daemon Service](https://beta.nomadphp.com/blog/50/creating-a-php-daemon-service). Now we are going to learn how to use methods to perform IPC - Inter-Process Communication - to communicate with daemon processes.

# Message Queues

In the world of UNIX, there is an incredible variety of ways to send a message or a command to a daemon script and vice versa. But first I want to talk only about message queues - "System V IPC Messages Queues".

A long time ago I learned that a queue can be either in the System V IPC implementation, or in the POSIX implementation. I want to comment only about the System V implementation, as I know it better.

Lets get started. At the "normal" operating system level, queues are stored in memory. Queue data structures are available to all system programs. Just as in the file system, it is possible to configure queues access rights and message size. Usually a queue message size is small, less than 8 KB.

This introductory part is over. Lets move on to the practice with same example scripts.

**queue-send.php**

```php

/ / Convert a path name and a project identifier to a System V IPC key

$key = ftok(__FILE__, 'A'); / / 555 for example

/ / Creating a message queue with a key, we need to use an integer value.

$queue = msg_get_queue($key);

/ / Send a message. Note that all required fields are already filled,

/ / but sometimes you want to serialize an object and put on a message or a lock.

/ / Note that we specify a different type. Type - is a certain group in the queue.

msg_send($queue, 1, 'message, type 1');

msg_send($queue, 2, 'message, type 2');

msg_send($queue, 3, 'message, type 3');

msg_send($queue, 1, 'message, type 1');

echo "send 4 messages

";

```

**queue-receive.php**

```php

$key = ftok('queue-send.php', 'A'); / / 555 for example

$queue = msg_get_queue($key);

/ / Loop through all types of messages.

for ($i = 1; $i <= 3; $i++) {

echo "type: {$i}

";

/ / Loop through all, read messages are removed from the queue.

/ / Here we find a constant MSG_IPC_NOWAIT, without it all will hang forever.

while ( msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT) ) {

echo "type: {$i}, msgtype: {$msgtype}, message: {$message}

";

}

}

```

Lets run on the first stage of the file queue-send.php, and then queue-receive.php.

```sh

u% php queue-send.php

send 4 messages

u% php queue-receive.php

type: 1

type: 1, msgtype: 1, message: s:15:"message, type 1";

type: 1, msgtype: 1, message: s:15:"message, type 1";

type: 2

type: 2, msgtype: 2, message: s:15:"message, type 2";

type: 3

type: 3, msgtype: 3, message: s:15:"message, type 3";

```

You may notice that the messages have been grouped. The first group gathered 2 messages of the first type, and then the remaining messages.

If we would have indicated to receive messages of type 0, you would get all messages, regardless of the type.

```php

while (msg_receive($queue, $i, $msgtype, 4096, $message, false, MSG_IPC_NOWAIT)) {

/ / ...

```

Here it is worth noting another feature of the queues: if we do not use the constant MSG_IPC_NOWAIT in the script and run the script queue-receive.php from a terminal, and then run periodically the file queue-send.php, we see how a daemon can effectively use this to wait jobs.

**queue-receive-wait.php**

```php

$key = ftok('queue-send.php', 'A'); / / 555 for example

$queue = msg_get_queue($key);

/ / Loop through all types of messages.

/ / Loop through all, read messages are removed from the queue.

while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {

echo "msgtype: {$msgtype}, message: {$message}

";

}

```

Actually that is the most interesting information of all I have said. There are also functions to get statistics, disposal and checking for the existence of queues.

Lets now try to write a daemon listening to a queue:

**queue-daemon.php**

```php

/ / Fork process

$pid = pcntl_fork();

$key = ftok('queue-send.php', 'A');

$queue = msg_get_queue($key);

if ($pid == -1) {

exit;

} elseif ($pid) {

exit;

} else {

while ( msg_receive($queue, 0, $msgtype, 4096, $message) ) {

echo "msgtype: {$msgtype}, message: {$message}

";

}

}

/ / Disengaged from the terminal

posix_setsid();

```

# Shared Memory

We have learned to work with queues, with which you can send small system messages. But then we may certainly be faced with the task of transmitting large amounts of data. My favorite type of system, System V, has solved the problem of rapid transmission and preservation of large data in memory using a mechanism called **Shared Memory**.

In short, the data in the Shared Memory lives until the system is rebooted. Since the data is in memory, it works much faster than if it was stored in a database somewhere in a file, or, God forgive me on a network share.

Lets try to write a simple example of data storage.

**shared-memory-write-base.php**

```php

/ / This is the correct and recommended way to obtain a unique identifier.

/ / Based on this approach, the system uses the inode table of the file system

/ / and for greater uniqueness converts this number based on the second parameter.

/ / The second parameter always goes one letter

$id = ftok(__FILE__, 'A');

/ / Create or open the memory block

/ / Here you can specify additional parameters, in particular the size of the block

/ / or access rights for other users to access this memory block.

/ / We can simply specify the id instead of any integer value

$shmId = shm_attach($id);

/ / As we have shared variables (any integer value)

$var = 1;

/ / Check if we have the requested variables.

if (shm_has_var($shmId, $var)) {

/ / If so, read the data

$data = (array) shm_get_var($shmId, $var);

} else {

/ / If the data was not there.

$data = array();

}

/ / Save the in the resulting array value of this file.

$data[time()] = file_get_contents(__FILE__);

/ / And writes the array in memory, specify where to save the variable.

shm_put_var($shmId, $var, $data);

/ / Easy?

```

Run this script several times to save the value in memory. Now lets write a script only to read from the memory.

**shared-memory-read-base.php**

```php

/ / Read data from memory.

$id = ftok(__DIR__ . '/shared-memory-write-base.php', 'A');

$shmId = shm_attach($id);

$var = 1;

/ / Check if we have the requested variables.

if (shm_has_var($shmId, $var)) {

$data = (array) shm_get_var($shmId, $var);

} else {

$data = array();

}

/ / Iterate received and save them to files.

foreach ($data as $key => $value) {

/ / A simple example, create a file from the data that we have saved.

$path = "/tmp/$key.php";

file_put_contents($path, $value);

echo $path . PHP_EOL;

}

```

# Semaphores

So, in general terms, it should be clear for you by now how to work with shared memory. The only problems left to figure out are about a couple of nuances, such as: "What to do if two processes want to record one block of memory?" Or "How to store binary files of any size?".

To prevent simultaneous accesses we will use semaphores. Semaphores allow us to flag that we want to have exclusive access to some resource, like for instance a shared memory block. While that happens other processes will wait for their turn on semaphore.

In this code it explained clearly:

**shared-memory-semaphors.php**

```php

/ / Let's try to save a binary file, the size of a couple of megabytes.

/ / This script does the following:

/ / If there is input, it reads it, otherwise it writes data into memory

/ / In this case, when writing to the memory we put a sign lock - semaphore

/ / Everything is as usual, read the previous comments

$id = ftok(__FILE__, 'A');

/ / Obtain a resource semaphore - lock feature. There is nothing wrong if we

/ / use the same id that is used to obtain a resource shared memory

$semId = sem_get($id);

/ / Put a lock. There's a caveat. If another process will encounter this lock,

/ / it will wait until the lock is removed

sem_acquire($semId);

/ / Specify your like picture

$data = file_get_contents(__DIR__.'/06050396.JPG', FILE_BINARY);

/ / These can be large, so precaution is necessary to allocate such a way that would be enough

$shmId = shm_attach($id, strlen($data)+4096);

$var = 1;

if (shm_has_var($shmId, $var)) {

/ / Obtain data from the memory

$data = shm_get_var($shmId, $var);

/ / Save our file somewhere

$filename = '/tmp/' . time();

file_put_contents($filename, $data, FILE_BINARY);

/ / Remove the memory block that started it all over again.

shm_remove($shmId);

} else {

shm_put_var($shmId, $var, $data);

}

/ / Releases the lock.

sem_release($semId);

```

Now you can use the md5sum command line utility to compare two files, the original and the saved file. Or, you can open the file in image editor or whatever prefer to compare the images.

With this we are done with shared memory and semaphores. As your homework I want to ask you to write code that a demon will use semaphores to access shared memory.

# Conclusion

Exchanging data between the daemons is very simple. This article described two options for data exchange: message queues and shared memory.

Post a comment here if you have questions or comments about how to exchange data with daemon services in PHP.

10034 views · 2 years ago

![Install Composer for PHP](https://images.ctfassets.net/vzl5fkwyme3u/6REkTRobXqOMgyiyCa8ioS/fb4fc03a6a5514df3522a605b981505b/composer.png?w=1000)

Composer is a must-have tool for every PHP developer these days. This page is a simple breakdown of quick-install instructions.

## How do I install composer?

1. Use PHP to download the composer installer, place it in the current directory, and name it `composer-setup.php`

2. Use PHP to check the hash of the file you downloaded and compare it to the known value of the hash. You can always find the current value of the hash for the installer on the [Composer Public Keys / Signatures](https://composer.github.io/pubkeys.html) page.

3. Run the setup program to install composer. This does more than just download the latest copy of composer, it also sets up your local ~/.composer directory. This will install composer into the current directory. You can add the --install-dir=DIR to specify where you want composer installed. You can also specify --filename=composer to change the installed filename. You can use anything you like that doesn’t already exist in your specified directory, you don’t have to use the name composer. This is a great way to get rid of the .phar at the end of the name if you don’t like it.

4. Use PHP to remove the installer from the current directory.

**To install composer for PHP you use PHP to download the installer, set a few options, and then actually perform the install.**

```php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"```

```php -r "if (hash_file('SHA384', 'composer-setup.php') === '55d6ead61b29c7bdee5cccfb50076874187bd9f21f65d8991d46ec5cc90518f447387fb9f76ebae1fbbacf329e583e30') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"```

```php composer-setup.php```

```php -r "unlink('composer-setup.php');"```

Below is a version you can copy ‘n paste.

Breakdown:

## Recommended Setup for Linux and macOS:

```

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"

php -r "if (hash_file('SHA384', 'composer-setup.php') === '55d6ead61b29c7bdee5cccfb50076874187bd9f21f65d8991d46ec5cc90518f447387fb9f76ebae1fbbacf329e583e30') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

php composer-setup.php --install-dir=/usr/local/bin --filename=composer

php -r "unlink('composer-setup.php');"```

**WARNING**: Line two IS WHERE IT compares the hasH of the installer you just downloaded to a hard-coded value. The value specified in the script is the value for the current version of the installer as of this writing. If it fails, check the [Composer Public Keys / Signatures](https://composer.github.io/pubkeys.html) page and get the latest version. Plug it into the script below and try again.

Windows user, change the `--installdir=` to a directory in your path.

## Recommended Setup for Windows

If you are running Microsoft Windows, the instructions above will work for you as long as you use the proper install paths. You can however download ComposerSetup.exe from the [Composer Introduction](https://getcomposer.org/doc/00-intro.md) page. Execute this and it will install composer and set your path so that you can run composer from anywhere. You will have to close your terminal window and open a new one after the install for the path to be updated.

That’s it, you should now have Composer installed.

## Composer installation Resources

* [Composer Homepage](https://getcomposer.org/)

* [Composer Introduction](https://getcomposer.org/doc/00-intro.md)

* [Download Composer](https://getcomposer.org/download/)

* [Composer Public Keys / Signatures](https://composer.github.io/pubkeys.html)

8257 views · 2 years ago

![How to install PHPUnit](https://images.ctfassets.net/vzl5fkwyme3u/3ekN0eVrp6IG48IQyYWgAe/12696dff66a9686b9ad5953643c18645/testing.png?w=1000)

PHPUnit is an essential tool for every PHP developers. It is one of those tools that every PHP developer should have installed in their development environment. The problems most first time PHPUnit developers run into are where to install it and how to install it. This quick guide will walk you through the process and answer both questions.

## How do I install PHPUnit

### The Easy Way

In your project’s root directory use this command.

`composer require --dev phpunit/phpunit ^6.0`

This command adds PHPUnit to your project as a development dependency. This is the absolute best way to install PHPUnit. It is the best way because this way the version of PHPUnit does not change unless you change it. We specified ^6.0 as the version which means we’ll get all the updates to the 6.0 branch but not 6.1. While BC breaks don’t happen often in PHPUnit, they have happened. If you have a globally installed version of PHPUnit and you upgrade it to a version that breaks BC, you have to go update all of your tests immediately. This is a lot of hassle if you have a lot of projects with a lot of tests. Keeping a copy of PHPUnit installed as a dev requirement in each project means that each project has its own copy that can be upgraded as necessary.

### The Hard Way

In a command prompt regardless of where you are in your file system, use this command.

`composer global require phpunit/phpunit ^6.0`

On MacOS and Linux machines, this will install PHPUnit in `~/.composer/vendor/bin`. If you add this directory to your path, then from any project, you can execute PHPUnit. However, as noted above, if you ever upgrade your globally installed packages then you will have problems.

`composer global update`

Run that when there is a new version of PHPUnit, it will be installed, regardless of whether this will break your existing unit tests on one or more of your projects. Windows users will need to locate the .composer/vendor/bin directory in your user’s home directory.

### The “ZOMG why would you do it this way” Way

Here is the old-school use wget and move it into the correct position manually. You can do it this way, but you will have to take care of all upgrades manually as well. If you only have a single project on the computer and you never ever plan on changing the version of PHPUnit….nope, still better to use`composer require --dev`.

`wget https://phar.phpunit.de/phpunit-6.0.phar`

`chmod +x phpunit-6.0.phar`

`sudo mv phpunit-6.0.phar /usr/local/bin/phpunit`

`phpunit --version`

These instructions are of course for MacOS or Linux. Windows user won’t need to do chmod or sudo but will need a BAT file.

That’s it. One of those commands should get you a working copy of PHPUnit on your computer.

## Resources:

* [Installing PHPUnit](https://phpunit.de/manual/current/en/installation.html)

* [Composer Introduction](https://getcomposer.org/doc/00-intro.md) (For the Global option)

    SPONSORS