Thumbnail Setting up a new PHP project

Door: Thijs Zumbrink
10-03-2017 12:13

In this article I will walk through the steps I take when setting up a new PHP project. I will explain the reasoning for every choice I make. I hope this will give insight to experienced PHP developers that haven't started a new project from scratch in a while but have instead "grown" into a framework. Many of the things mentioned here are also recognizable in other languages.

Before diving into the details, here are the general steps:
• Composer.json (project file and dependencies)
• Standard files and directories
• First trivial file with test
• Editor setup
• Version control
• Composer scripts
• Continuous integration
• Build and deployment (optional)

If following this as a guide, I assume that the following things are already installed on your machine:
• PHP
• Composer
• Editor (Netbeans)
• Version control software (Git)

Composer.json

The project begins with a single file called composer.json. It lists some properties about the project, such as the name, description and author(s). But most importantly, it lists the dependencies that Composer will automatically download for your project.

It can be created with the command composer init. Simply create the project directory, cd into it, and issue the command. It will walk through a list of questions to fill in relevant details. Note that it's no problem if you mess this up, because the resulting file that gets created is readable and can be edited to change anything.

The requirements depend heavily on the project itself. If I already know what I need, I specify them here, otherwise I do that later. For the development requirements however, I usually have a fixed set of libraries:
• phpunit/phpunit: Unit testing framework
• phpstan/phpstan: Static analysis
• phing/phing: Build and deployment tool (if applicable)

They are called development requirements, because they are only needed for me as an author, not to actually function in production.

I only use Phing for projects that need build and deployment, i.e. web applications. If I'm writing a library however, it will not contain any configuration and doesn't need to end up on a web server, so I have no need for it.

Lastly we need to enter some directives to satisfy the (PSR-4) autoloader. This means that our chosen namespace will resolve to the correct paths. See an example composer.json.

When the composer.json file is created, run composer install to install the libraries. It will create a composer.lock file and a vendor/ directory.

Standard files and directories

Some files and directory exist in every project, so it makes sense to create them right at the start. However, if you are bound to a particular framework, it's probably best to follow the setup guide of that framework, then use this list for anything that was not covered.

src/ directory that will house all application code files. If writing a library, this is everything that another party needs to actually use it. If writing a project, this is what's needed to run it. (Excluding configuration.)

tests/ directory that will house the test code files.

phpunit.xml configuration for unit testing framework. This tells where the test directory is and how the bootstrap file is called. Example phpunit.xml.

tests/bootstrap.php unit testing preparations. Usually it just includes Composer's autoloader. Example bootstrap.php.

README.md general project documentation. When open-sourcing the project on GitHub or GitLab, the contents of this file will be shown on the main project page.

LICENSE.md if the project has a particular license (for example an open source project) then this file makes a lot of sense. For proprietary projects that others will never see, feel free to leave it out or put whatever copyright notice your company uses.

First trivial file with test

To make sure that the project is able to run, I prefer to create simple source and test files, then run the test file via the testing framework. If it reports success with one test executed, we're good to go!

The test file can be anything, for example a class that just has a property, constructor and getter. The test then constructs an object, calls the getter and sees if the result is correct. These files will be deleted again later, they just serve to check that everything is hooked up correctly.

To run the test, assuming phpunit.xml is present in the project directory, just run the command vendor/bin/phpunit. It will automatically use the file to find the test and run it.

For web applications, I usually create a src/web/index.php file with some print statement, then run PHP's built-in web server by issuing php -S localhost:8000 -t src/web/ from the project directory. Browsing to the server address will show the page.

Editor setup

Now is a good time to set up the editor. This step highly depends on what you are used to. I use an IDE (Integrated Development Environment), Netbeans to be precise. The exact choice is not really important, but what matters most is what kind you use: an IDE (Netbeans, Eclipse, PHPStorm, ...) or a plain editor (Sublime, Notepad++, ...). IDE's require some amount of setup, because they integrate with all kinds of tools used for checking, testing, building and deploying. Editors on the other hand do not require setup, so in that case you can skip this step.

In Netbeans I "create a new project from existing sources". I specify the project root for the sources, and I make sure the Netbeans metadata is saved in a different directory. This is because I like to keep my projects free of specific setups, and leave that as much as possible to the preferences of the programmer. On the other hand, when working in a team with standardized tooling, it's no problem to put the project metadata into the project directory itself. That way, all programmers share the same configuration.

After the project is created, more settings can be changed by going to the project properties. In particular, I set the correct PHP version (the nightly Netbeans version has support for PHP 7 for ages already, hopefully they will release it some day...) If the project is a web application, I configure it to run using PHP's built in web server, as mentioned previously. The src/web/ directory becomes the web root. This enables convenient execution via the run button in the top. If it's a library, the run configuration will not be used.

Netbeans has good support for PHPUnit, so I enable it and set a few settings:
• I use the project's own phpunit executable: vendor/bin/phpunit. This makes sure that the test suite is compatible with the specific version used, and doesn't depend on system-wide installation of phpunit.
• I point to the phpunit.xml configuration file.
• Even though a "bootstrap file" is mentioned here, it's not required to fill this in. PHPUnit will find it via the configuration file already.
• Lastly, I tell PHPUnit to handle all .php test files. If using multiple test frameworks together, this setting could change.

To check if everything is working, I perform the same checks as earlier (trivial test case or index.php) but this time via the IDE. Right clicking on the project should run the tests and show the results. In case of a web application, the run button should start the server and open the browser. I also verify xdebug integration by setting a break point and running the tests in debug mode. This always works out of the box for me in a new project, but in the beginning I had to configure PHP and Netbeans for it.

Version control

While some would start with version control right before doing anything else, I like to introduce it only here. The reason is that I want the first commit to be a collection of files that can already run, to provide a good basis for the rest of the work.

While the general idea should be similar for every type of version control, I use Git, which integrates easiest with the other used services (GitHub, Composer, Continuous Integration tools, ...)

To choose what goes into the first commit, the answer is basically: "everything except for a few specific files". These files are:
• The vendor/ directory, since these can be fetched by Composer.
• The composer.lock file if working on a library. If working on a full project, this file should get committed as well.
• Any user-specific editor files, this depends on the editor in question and only if it saves them inside the project directory. You might want to commit the project-specific editor files while ignoring the user-specific ones.

This should form the basis for the .gitignore file. (Example) Depending on the build process, it should be extended with things like build artifacts and configuration. Any application-written data such as uploads or logs must also be ignored in case you run the project directly from the source, instead of putting a build step in between.

After making the commit, I usually create a new project in GitHub and/or GitLab, fill in the name and description, and add the repository URL as a remote. After pushing the initial commit to the master branch, it should appear on the project page.

Composer scripts

Composer provides the ability to add "scripts": aliases to commands so they are easier to issue. For example, instead of:
vendor/bin/phpstan analyse --level 5 src tests


you can call
composer stan


In order to do this, composer needs to know which aliases execute which commands. An alias can also refer to other aliases or multiple commands. For more information on this topic, see the manual.

I like to define the following aliases:
stan: perform static analysis
check: run the above, and any other checks like style checking
test: run the test suite
ci: run all of the above, convenient single command for the continuous integration server

These are included in this example composer.json.

The list can be extended with any aliases that you find convenient. For example, you may want to house build and deployment scripts here. However, keep in mind that this can be limited, so more will be explained in the section on build and deployment.

Continuous integration

This step depends on whether you want it and what the nature of the project is. If it's open source, CI services likely offer themselves for free. I mainly use TravisCI.

TravisCI will react to every commit you push to GitHub and every pull request that gets submitted. Thanks to the nice integration, it will show a green check mark if everything is okay, or a red cross if there are problems. Additionally you will get an e-mail whenever the tests fail, in case you made a mistake and forgot to run the test suite before committing, or if the tests are brittle / not portable enough.

To set up TravisCI, you need to create an account there and refer to the GitHub account. The integration is then as easy as turning on the switch for your project. To make it functional, the project repository needs a .travis.yml file. Here you can tell on which versions of PHP you want to test, how to install the project, and how to test. See an example .travis.yml file.

Build and deployment

Optionally, if the project needs to be hosted on a web server, it needs a script for building and deploying. By automating this step (maybe even taking it to the extreme with continuous deployment) you increase productivity and prevent mistakes.

The build step is responsible for making sure that your source code is properly transformed into a running application. This includes installing composer libraries without the development libraries. But also other steps like generating CSS files from SCSS, minify your JavaScript, generate/resize images from source files, you name it. But most importantly: configuration. Your project likely relies on a database or an external service for which you want to keep the access credentials secret. In that case, the build step takes your (unversioned) configuration file, your source code, and creates a build artifact in which the configuration is properly integrated. The build artifact should be a directory / archive file that is sufficient to run your application in production.

The deploy step takes the build artifact and makes sure it ends up on the web server. I prefer to copy the file(s) over SSH and use a rename or a symlink to point the web server to the correct location.

To do these tasks, I prefer Phing. It's a PHP library, which means it can be integrated in the project using Composer, ensuring that the configuration file is compatible with the version used. I explained my process in more detail here. It also details the web server configuration.

Conclusion

In this post I detailed the standard steps that I follow for any new project. Since most of my projects are bare PHP libraries, not tied to a framework, I focused most on that aspect. Did I miss anything? Did you learn anything new or encounter problems? Please let me know!

Reacties
Log in of registreer om reacties te plaatsen.
Thijs Zumbrink, 29-03-2017 11:22:
Update: I just found out that PHPUnit will already look for Composer's autoload.php itself. Therefore it is not needed to include the autoloader in the bootstrap.php file, and you can even skip bootstrapping altogether if there are no other steps to take. Then again, explicitly doing it will not hurt, as it can prevent confusion for the next reader.

Thijs Zumbrink, 02-04-2017 14:11:
Another update: PHPUnit can generate an initial configuration file by executing "phpunit --generate-configuration". By default, it uses vendor/autoload.php as bootstrap script, so we don't need bootstrap.php anyway unless other tasks are needed.