Thursday, 23 November 2023

Automating the backslash prefixing for native PHP function calls

After reading the blog post Why does a backslash prefix improve PHP function call performance by Jeroen Deviaene I was looking for a way to automate it for the codebase of the Lean Package Validator, to shave off some miliseconds for it's CLI. The PHP Coding Standards Fixer has a rule named native_function_invocation which does the very exact task.

Configuring the PHP Coding Standards Fixer

.php-cs-fixer.php
<?php

use PhpCsFixer\Config;
use PhpCsFixer\Finder;

$finder = Finder::create()
    ->in([__DIR__, __DIR__ . DIRECTORY_SEPARATOR . 'tests']);

$rules = [
    'psr_autoloading' => false,
    '@PSR2' => true,
    'phpdoc_order' => true,
    'ordered_imports' => true,
    'native_function_invocation' => [
        'include' => ['@internal'],
        'exclude' => ['file_put_contents']
    ]
];

$cacheDir = \getenv('HOME') ? \getenv('HOME') : __DIR__;

$config = new Config();

return $config->setRules($rules)
    ->setFinder($finder)
    ->setCacheFile($cacheDir . '/.php-cs-fixer.cache');
To make this rule executeable I needed to add the --allow-risky=yes option to the PHP Coding Standards Fixer calls in the two dedicated Composer scripts shown next.

composer.json
"scripts": {
    "lpv:test": "phpunit",
    "lpv:test-with-coverage": "export XDEBUG_MODE=coverage && phpunit --coverage-html coverage-reports",
    "lpv:cs-fix": "php-cs-fixer --allow-risky=yes fix . -vv || true",
    "lpv:cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run --allow-risky=yes",
    "lpv:configure-commit-template": "git config --add commit.template .gitmessage",
    "lpv:application-version-guard": "php bin/application-version --verify-tag-match=bin",
    "lpv:application-phar-version-guard": "php bin/application-version --verify-tag-match=phar",
    "lpv:static-analyse": "phpstan analyse --configuration phpstan.neon.dist", 
    "lpv:validate-gitattributes": "bin/lean-package-validator validate"
},
After running the lpv:cs-fix Composer script the first time the tests of the system under test started failing due to file_put_contents being prefixed with a backslash when using phpmock\MockBuilder's setName method, so I had to exclude it as shown in the PHP Coding Standards Fixer configuration above.

Monday, 15 January 2018

Documenting Composer scripts

For open source projects I'm involved with, I developed the habit to define, and document the steady growing amount of repository and build utilities via Composer scripts. Having Composer scripts available makes it trivial to define aliases or shortcuts for complex and hard to remember CLI calls. It also lowers the barrier for contributors to start using these tools while helping out with fixing bugs or providing new features. Finally they're also simplifying build scripts by stashing away complexity.

Defining Composer scripts

If you've already defined or worked with Composer scripts or even their npm equivalents you can skip this section, otherwise the next code snippet allows you to study how to define these. The here defined Composer scripts range from simple CLI commands with set options (e.g. the test-with-coverage script) to more complex build utility tools (i.e. the application-version-guard script) which are extracted into specific CLI commands to avoid cluttering up the composer.json or even the .travis.yml.

composer.json
{
  "scripts": {
    "test": "phpunit",
    "test-with-coverage": "phpunit --coverage-html coverage-reports",
    "cs-fix": "php-cs-fixer fix . -vv || true",
    "cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "configure-commit-template": "git config --add commit.template .gitmessage",
    "application-version-guard": "php bin/application-version --verify-tag-match"
  }
}

Describing Composer scripts

Since Composer 1.6.0 it's possible to set custom script descriptions via the scripts-descriptions element like shown next. It's to point out here that the name of a description has to match the name of a defined custom Composer script to be recognised at runtime. On another note it's to mention that the description should be worded in simple present to align with the other Composer command descriptions.

composer.json
{
  "scripts-descriptions": {
    "test": "Runs all tests.",
    "test-with-coverage": "Runs all tests and measures code coverage.",
    "cs-fix": "Fixes coding standard violations.",
    "cs-lint": "Checks for coding standard violations.",
    "configure-commit-template": "Configures a local commit message template.",
    "application-version-guard": "Checks that the application version matches the given Git tag."
  },
  "scripts": {
    "test": "phpunit",
    "test-with-coverage": "phpunit --coverage-html coverage-reports",
    "cs-fix": "php-cs-fixer fix . -vv || true",
    "cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "configure-commit-template": "git config --add commit.template .gitmessage",
    "application-version-guard": "php bin/application-version --verify-tag-match"
  }
}
Now when running $ composer via the terminal the descriptions of defined custom scripts will show up sorted in into the list of available commands, which makes it very hard to spot the Composer scripts of the package at hand. Luckily Composer scripts can also be namespaced.

Namespacing Composer scripts

To namespace (i.e. some-namespace) the custom Composer scripts for any given package define the script names with a namespace prefix as shown next. As the chances are very high that you will be using the one or other Composer script several times, while working on the package, it's recommended to use a short namespace like in the range from two to four characters.

composer.json
{
  "scripts": {
    "some-namespace:test": "phpunit",
    "some-namespace:test-with-coverage": "phpunit --coverage-html coverage-reports",
    "some-namespace:cs-fix": "php-cs-fixer fix . -vv || true",
    "some-namespace:cs-lint": "php-cs-fixer fix --diff --stop-on-violation --verbose --dry-run",
    "some-namespace:configure-commit-template": "git config --add commit.template .gitmessage",
    "some-namespace:application-version-guard": "php bin/application-version --verify-tag-match"
  }
}
Now this time when running $ composer via the terminal the defined custom scripts will show up in the list of available commands in a namespaced manner giving an immediate overview of the available Composer script of the package at hand.

$ composer
 ... ommitted content
Available commands:
  ... ommitted content
 some-namespace
  some-namespace:application-version-guard  Checks that the application version matches the given Git tag.
  some-namespace:configure-commit-template  Configures a local commit message template.
  some-namespace:cs-fix                     Fixes coding standard violations.
  some-namespace:cs-lint                    Checks for coding standard violations.
  some-namespace:test                       Runs all tests.
  some-namespace:test-with-coverage         Runs all tests and measures code coverage.
To use any namespaced Composer script, e.g. to fix coding standard violations after a substantial refactoring, it has to be called with its namespace e.g.$ composer some-namespace:cs-fix, which is the one disadavantage of Composer script namespacing.

Saturday, 25 March 2017

Keeping your CLI integration tests green on Windows

Lately on a Windows system, some failing integration tests for CLI commands utilising the Symfony Console component caused me some blip headaches by PHPUnit insisting that two strings are not identical due to different line endings. The following post documents the small steps I took to overcome these headaches.

First the assertion message produced by the failing test, see the console output below, got me thinking it might be caused by different encodings and line endings; though the project was utilising an .editorconfig from the early start and the related files were all encoded correctly and had the configured line endings. The Git configuration e.g. core.autocrlf=input also was as it should be.

1) Stolt\LeanPackage\Tests\Commands\InitCommandTest::createsExpectedDefaultLpvFile
Failed asserting that two strings are identical.
--- Expected
+++ Actual
@@ @@
 #Warning: Strings contain different line endings!
-Created default 'C:\Users\stolt\AppData\Local\Temp\lpv\.lpv' file.
+Created default 'C:\Users\stolt\AppData\Local\Temp\lpv\.lpv' file.
Another deeper look at the CommandTester class yielded that it’s possible to disable the command output decoration and also to normalise the command output. So a change of the SUT preparation and a normalisation of the console output, visualised via a git diff -U10, brought the solution for this particular test.

diff --git a/tests/Commands/InitCommandTest.php b/tests/Commands/InitCommandTest.php
index 58e7114..fb406f3 100644
--- a/tests/Commands/InitCommandTest.php
+++ b/tests/Commands/InitCommandTest.php
@@ -48,21 +48,21 @@ class InitCommandTest extends TestCase
     /**
      * @test
      */
     public function createsExpectedDefaultLpvFile()
     {
         $command = $this->application->find('init');
         $commandTester = new CommandTester($command);
         $commandTester->execute([
             'command' => $command->getName(),
             'directory' => WORKING_DIRECTORY,
-        ]);
+        ], ['decorated' => false]);

// ommitted  code

-        $this->assertSame($expectedDisplay, $commandTester->getDisplay());
+        $this->assertSame($expectedDisplay, $commandTester->getDisplay(true));
         $this->assertTrue($commandTester->getStatusCode() == 0);
         $this->assertFileExists($expectedDefaultLpvFile);
Since the SUT had a lot of integration test for its CLI commands, the lazy me took the shortcut to extend the CommandTester and using it, with desired defaults set, instead of changing all of the related command instantiations.

<?php

namespace SUT\Tests;

use Symfony\Component\Console\Tester\CommandTester as ConsoleCommandTester;

class CommandTester extends ConsoleCommandTester
{
    /**
     * Gets the display returned by the last execution of the command.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    public function getDisplay($normalize = true)
    {
        return parent::getDisplay($normalize);
    }

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive: Sets the input interactive flag
     *  * decorated:   Sets the output decorated flag
     *  * verbosity:   Sets the output verbosity flag
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(
        array $input,
        array $options = ['decorated' => false]
    ) {
        return parent::execute($input, $options);
    }
}
So it's a yay for green CLI command integration tests on Windows from here on. Another measure for the SUT would be to enable Continuous Integration on a Windows system via AppVeyor, but that’s a task for another commit 85bdf22.

Monday, 10 October 2016

Eight knobs to adjust and improve your Travis CI builds

After having refactored several Travis CI configuration files over the last weeks, this post will provide eight adjustments or patterns immediately applicable for faster, changeable, and economic builds.

1. Reduce git clone depth

The first one is a simple configuration addition with a positive impact on the time and disk space consumption, which should be quite noticeable on larger code bases. Having this configured will enable shallow clones of the Git repository and reduce the clone depth from 50 to 2.

.travis.yml
git:
  depth: 2

2. Enable caching

The second one is also a simple configuration addition for caching the Composer dependencies of the system under build (SUB) or its result of static code analysis. Generally have a look if your used tools allow caching and if so cache away. This one deserves a shout out to @localheinz for teaching me about this one.

The next shown configuration excerpt assumes that you lint coding standard compliance with the PHP Coding Standards Fixer in version 2.0.0-alpha and have enable caching in its .php_cs configuration.

.travis.yml
cache:
  directories:
    - $HOME/.composer/cache
    - $HOME/.php-cs-fixer

3. Enforce contribution standards

This one might be a tad controversial, but after having had the joys of merging GitHub pull requests from a master branch I started to fail builds not coming from feature or topic branch with the next shown bash script. It's residing in an external bash script to avoid the risk of terminating the build process.

./bin/travis/fail-non-feature-topic-branch-pull-request
#!/bin/bash
set -e
if [[ $TRAVIS_PULL_REQUEST_BRANCH = master ]]; then
  echo "Please open pull request from a feature / topic branch.";
  exit 1;
fi

.travis.yml
script:
  - ./bin/travis/fail-non-feature-topic-branch-pull-request
The bash script could be extended to fail pull requests not following a branch naming scheme, e.g. feature- for feature additions or fix- for bug fixes, by evaluating the branch name. If this is a requirement for your builds you should also look into the blocklisting branches feature of Travis CI.

4. Configure PHP versions in an include

With configuring the PHP versions to build against in a matrix include it's much easier to inject enviroment variables and therewith configure the version specific build steps. You can even set multiple enviroment variables like done on the 7.0 version.

.travis.yml
env:
  global:
    - OPCODE_CACHE=apc

matrix:
  include:
    - php: hhvm
    - php: nightly
    - php: 7.1
    - php: 7.0
      env: DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: 
      - DISABLE_XDEBUG=true

before_script:
  - if [[ $DISABLE_XDEBUG = true ]]; then
      phpenv config-rm xdebug.ini;
    fi
I don't know if enviroment variable injection is also possible with the minimalistic way to define the PHP versions list, so you should take that adjustment with a grain of salt.

It also seems like I stumbled upon a Travis CI bug where the global enviroment variable OPCODE_CACHE is lost, so add another grain of salt. To work around that possible bug the relevant configuration has to look like this, which sadly adds some duplication and might be unsuitable when dealing with a large amount of environment variables.

.travis.yml
matrix:
  include:
    - php: hhvm
      env: 
      - OPCODE_CACHE=apc
    - php: nightly
      env: 
      - OPCODE_CACHE=apc
    - php: 7.1
      env: 
      - OPCODE_CACHE=apc
    - php: 7.0
      env: OPCODE_CACHE=apc DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: OPCODE_CACHE=apc DISABLE_XDEBUG=true

before_script:
  - if [[ $DISABLE_XDEBUG = true ]]; then
      phpenv config-rm xdebug.ini;
    fi

5. Only do static code analysis or code coverage measurement once

This one is for reducing the build duration and load by avoiding unnecessary build step repetition. It's achived by linting against coding standard violations or generating the code coverage for just a single PHP version per build, in most cases it will be the same for 5.6 or 7.0.

.travis.yml
matrix:
  include:
    - php: hhvm
    - php: nightly
    - php: 7.1
    - php: 7.0
      env: DISABLE_XDEBUG=true LINT=true
    - php: 5.6
      env: 
      - DISABLE_XDEBUG=true

script:
  - if [[ $LINT=true ]]; then
      composer cs-lint;
      composer test-test-with-coverage;
    fi

6. Only do release releated analysis and checks on tagged builds

This one is also for reducing the build duration and load by targeting the analysis and checks on tagged release builds. For example if you want to ensure that your CLI binary version, the one produced via the --version option, matches the Git repository version tag run this check only on tagged builds.

.travis.yml
script:
  - if [[ ! -z "$TRAVIS_TAG" ]]; then
      composer application-version-guard;
    fi

7. Run integration tests on very xth build

For catching breaking changes in interfaces or API's beyond your control it makes sense do run integration tests against them once in a while, but not on every single build, like shown in the next Travis CI configuration excerpt which runs the integration tests on every 50th build.

.travis.yml
script:
  - if [[ $(( $TRAVIS_BUILD_NUMBER % 50 )) = 0 ]]; then
      composer test-all;
    else
      composer test;
    fi

8. Utilise Composer scripts

The last one is all about improving the readability of the Travis CI configuration by extracting command configurations i.e. options into dedicated Composer scripts. This way the commands are also available during your development activitives and not hidden away in the .travis.yml file.

composer.json
{
    "__comment": "omitted other configuration",
    "scripts": {
        "test": "phpunit",
        "test-with-coverage": "phpunit --coverage-html coverage-reports",
        "cs-fix": "php-cs-fixer fix . -vv || true",
        "cs-lint": "php-cs-fixer fix --diff --verbose --dry-run"
    }
}
To ensure you don't end up with an invalid Travis CI configuration, which might be accidently committed, you can use composer-travis-lint a simple Composer script linting the .travis.yml with the help of the Travis CI API.

Happy refactoring.

Tuesday, 20 September 2016

Anatomy of a dope PHP package repository

While contributing to Construct, maintained by Jonathan Torres, I gathered some insights and learnings on the characteristics of a dope PHP package repository. This post summarises and illustrates these, so that PHP package develeopers have a complementary guideline to improve existing or imminent package repositories. Jonathan Reinink did a good job in putting the PHP package checklist out there which provides an incomplete, but solid quality checklist for open-source PHP packages.

I'll distill the characteristics of a dope PHP package repository by looking at the repository artifacts Construct can generate for you when starting the development of a new PHP project or micro-package. The following tree command output shows most of the elements this post will touch upon. The artifacts in parenthese are optional and configurable from Construct but can nonetheless have an import impact on the overall package quality.

├── <package-name>
│   ├── CHANGELOG.md
│   ├── (CONDUCT.md)
│   ├── composer.json
│   ├── composer.lock
│   ├── CONTRIBUTING.md
│   ├── (.editorconfig)
│   ├── (.env)
│   ├── (.env.example)
│   ├── (.git)
│   │   └── ...
│   ├── .gitattributes
│   ├── (.github)
│   │   ├── CONTRIBUTING.md
│   │   ├── ISSUE_TEMPLATE.md
│   │   └── PULL_REQUEST_TEMPLATE.md
│   ├── .gitmessage
│   ├── .gitignore
│   ├── (.lgtm)
│   ├── LICENSE.md
│   ├── (MAINTAINERS)
│   ├── (.php_cs)
│   ├── (phpunit.xml.dist)
│   ├── README.md
│   ├── (docs)
│   │   └── index.md
│   ├── src
│   │   └── Logger.php
│   ├── tests
│   │   └── LoggerTest.php
│   ├── .travis.yml
│   ├── (Vagrantfile)
│   └── vendor
│           └── ...

Definition of a dope PHP package repository

Before jumping into the details, let's define what could be considered as a dope package repository. Therefor, being lazy, I'm going to simply reword this classic quote from Michael Feathers
> Clean code is code that is written by someone who cares.
to
> A dope PHP package repository is one that is created and maintained by someone who cares.

Artifact categories

The next shown pyramid illustrates the three main categories the artifacts of a package repository will fall into.
First and most important there's the main sourcecode, it's tests or specs, and the documentation which could be dependent on it's size reside in a README.md section or inside a dedicated docs directory. Using a docs directory also allows publishing the documentation via GitHub pages. Other aspects of a package which should be documented are the chosen license, how to contribute to the package, possibly a code of conduct to comply with, and the changes made over the lifespan of the package.

Second there's the configuration for a myriad of tools like Git, GitHub, EditorConfig, Composer, the preferred testing framework, the preferred continuous inspection / integration platform such like Scrutinizer or Travis CI, and so forth.

The final category includes tools which ease the life of maintainers and potential contributors equally. These tools can be helpful for releasing new versions, enforcing coding standard compliance, or commit message quality and consistency.

Consistency

Sourcecode

All sourcecode and accompanying tests or specs should follow a coding standard (PSR-2) and have a consistent formatting style, there's nothing new here. The perfect place to communicate such requirements is the CONTRIBUTING.md file.

Tools like PHP Coding Standards Fixer or PHP_CodeSniffer in combination with a present configuration .php_cs|ruleset.xml.dist and a command wrapping Composer script are an ideal match to ease compliance. The Composer script cs-fix shown next will be available for maintainers and contributors alike.

composer.json
{
    "__comment": "omitted other configuration",
    "scripts": {
        "cs-fix": "php-cs-fixer fix . -vv || true"
    }
}
Consistent formatting styles like line endings, indentation style, and file encoding can be configured via an EditorConfig configuration residing in .editorconfig which will be used when supported by the IDE or text editor of choice.

Artifact naming and casing

Like sourcecode formatting and naming, repository artifacts should also follow a predictable naming scheme. All documentation files should have a consistent extension like .md or .rst and the casing should be consistent throughout the package repository. Comparing
├── <package-name>
│   ├── changelog.md
│   ├── code_of_conduct.md
│   ├── ...
│   ├── .github
│   │   └── ...
│   ├── LICENSE
│   ├── Readme.md
│   ├── roadmap.rst
to
├── <package-name>
│   ├── CHANGELOG.md
│   ├── CODE_OF_CONDUCT.md
│   ├── ...
│   ├── .github
│   │   └── ...
│   ├── LICENSE.md
│   ├── README.md
│   ├── ROADMAP.md
I would favour the later one anytime for it's much easier reading flow and pattern matchableness. The easier reading flow is achieved by the upper casing of the *.md files which also clearly communicates their documentation character.

The configuration files for tools which except the .dist file extension per default should all have such one like shown next.
├── <package-name>
│   ├── build.xml.dist
│   ├── phpunit.xml.dist
│   ├── ruleset.xml.dist
│   ├── ...

Commit message format

Next to the package's changelog, incrementally growing in the CHANGELOG.md file, the Git commit messages are an important source of change communication. Therefor they should also follow a consistent format which improves the reading flow while also leaving a professional impression. This format can be documented once again in the CONTRIBUTING.md file or even better be provided via a .gitmessage file residing in the package's Git repository.

Once more a Composer script, named configure-commit-template here, can ease configuration and if configured Git will use it's content when committing without the -m|--message and -F|--file option.

composer.json
{
    "__comment": "omitted other configuration",
    "scripts": {
        "configure-commit-template": "git config --add commit.template .gitmessage"
    }
}
To enforce commit message formatting adherence to the rules described by Chris Beams on a Git hook level, the git-lint-validators utility by Billie Thompson can be helpful.

Versioning

Release versions should follow the semantic versioning specification aka SemVer, once again there's nothing new here. When using version numbers in the sourcecode or CLI binaries, these should be in sync with the set Git tag. Tools like RMT or self-written tools should be utilised for this mundane task.

The next shown code illustrates such a simple self-written tool named application-version. It's main purpose is to set the provided version number in the CLI application's binary and avoid an application version and Git tag mismatch.

bin/application-version
#!/usr/bin/env php
<?php
$binApplicationName = '<bin-application-name>';
$binFile = __DIR__ . DIRECTORY_SEPARATOR . $binApplicationName;
list($void, $binFileRelative) = explode($binApplicationName, $binFile, 2);
$shortBinFilePath = $binApplicationName . $binFileRelative;

$options = getopt('v:ch', ['version:', 'current', 'verify-tag-match', 'help', 'current-raw']);

$help = <<<HELP
This command sets the version number in the {$shortBinFilePath} file:
Usage:
  application-version [options]
Options:
  -c, --current, --current-raw   The current version number
  --verify-tag-match             Verify application version and Git tag match
  -v, --version                  The version number to set
  -h, --help                     Display this help message
HELP;

if (array_key_exists('h', $options) || array_key_exists('help', $options)) {
    echo $help;
    exit(0);
}

/**
 * Return the application version.
 *
 * @param  string $binFile File holding the application version.
 * @return string
 */
function get_application_version($binFile) {
    $matches = [];
    $match = preg_match(
        '/(\d+\.)?(\d+\.)?(\*|\d+)/',
        file_get_contents($binFile),
        $matches
    );
    return trim($matches[0]);
}
/**
 * Return latest tagged version.
 *
 * @return string
 */
function get_latest_tagged_version() {
    exec('git describe --tags --abbrev=0', $output);
    return trim($output[0]);
}

if (array_key_exists('verify-tag-match', $options)) {
    $applicationVersion = 'v' . get_application_version($binFile);
    $latestGitTag = get_latest_tagged_version();
    if ($applicationVersion === $latestGitTag) {
        echo "The application version and Git tag match on {$latestGitTag}." . PHP_EOL;
        exit(0);
    }
    echo "The application version {$applicationVersion} and Git tag {$latestGitTag} don't match." . PHP_EOL;
    exit(1);
}

if (array_key_exists('current-raw', $options)) {
    echo get_application_version($binFile) . PHP_EOL;
    exit(0);
}

if (array_key_exists('c', $options) || array_key_exists('current', $options)) {
    $applicationVersion = 'v' . get_application_version($binFile);
    $latestGitTag = get_latest_tagged_version();
    echo "Current version set in {$shortBinFilePath} is {$applicationVersion}." . PHP_EOL;
    echo "Current tagged version {$latestGitTag}." . PHP_EOL;
    exit(0);
}

if ($options === []) {
    echo 'No options set.' . PHP_EOL;
    exit(1);
}

$version = isset($options['version']) ? trim($options['version']) : trim($options['v']);
$fileContent = file_get_contents($binFile);
$fileContent = preg_replace(
    '/(.*define.*VERSION.*)/',
    "define('VERSION', '$version');",
    $fileContent
);
file_put_contents($binFile, $fileContent);
echo "Set version in {$shortBinFilePath} to {$version}." . PHP_EOL;
exit(0);
The application-version tool could further be utilised in Travis CI builds, to avoid the earlier mentioned version differences, like shown in the next .travis.yml diggest. On an application version and Git tag mismatch the shown build script will break the build early.

.travis.yml
language: php

# omitted other configuration

script:
  # Verify application version and Git tag match
  - php bin/application-version --verify-tag-match
  # omitted other scripts

Lean builds

To speed up continuous integration builds, resource and time consuming extensions like Xdebug should be disabled when not required for measuring code coverage. The next shown before_script, tailored for Travis CI, is generated by Construct per default and might shave off a few build seconds and thereby provide a faster feedback.

.travis.yml
language: php

# omitted other configuration

before_script:
  - phpenv config-rm xdebug.ini || true
  # omitted other before_scripts
To reduce email traffic, the email notifications send by Travis CI should be reduced to a minimum like shown next, or dependent on your workflow the could be disabled at all.

.travis.yml
language: php

# omitted other configuration

notifications:
  email:
    on_success: never
Something I really would love to be supported by Travis CI is a feature to ignore a set of definable artifacts which could be configured in a .buildignore file or the like. This way wording or spelling changes on non build relevant artifacts like the README.md wouldn't trigger a build and misspend resources and energy. There's a related GitHub issue and here's hope it will be revisited in the near future.

Lean releases

To keep releases (or dists in Composer lingo) of PHP projects or micro-packages as lean as possible, their repositories should contain a complete and valid .gitattributes file. With such a file present all export-ignored files will be excluded from release archives and thereby save a significant amount of bandwith and energy.

The next code shows the content of such a .gitattributes file excluding non release relevant files like internal tools, configuration, and documentation artifacts. If for some reasons you require the complete source of a PHP project or micro-package you can bypass the default by using Composer's --prefer-source option.

.gitattributes
* text=auto eol=lf

.editorconfig export-ignore
.gitattributes export-ignore
.github/ export-ignore
.gitignore export-ignore
.gitmessage export-ignore
.php_cs export-ignore
.travis.yml export-ignore
bin/application-version export-ignore
bin/release-version export-ignore
bin/start-watchman export-ignore
CHANGELOG.md export-ignore
LICENSE.md export-ignore
phpunit.xml.dist export-ignore
README.md export-ignore
tests/ export-ignore
To validate the .gitattributes file of a PHP project or micro-package on the repository, Git HEAD, or build level the LeanPackageValidator CLI can be helpful.

Avoid badge posing

Badges, if used sparsely, are a handy tool for visualising some of the repository properties. It's definitely nice to immediately see the required PHP version, the current build status, or the latest version of the package as they save you manual look ups.

Badges showing the amount of downloads, code coverage, or the chosen license are in my opinion kind of poserish, they cause unnecessary requests to the badge service, and are in case of the license even obsolete.

Why should you care about the dopeness of a PHP package repository?

Creating and maintaining a dope PHP package repository might have a positive impact on several levels. It can earn you some valuable Brownie points or even provide a conversation gambit when doing job interviews, simply because you showcase professionalism.

Furthermore it's more likely to get valuable and high quality contributions from your second target audience when supportive documentation and tooling for things like issue creation, coding standards compliance, or Git commit message consistency are available.

It also might convince an end user, your main target audience, in using your package over a competitive one.

Le fini

So these were my recent insights and learnings on the anatomy of a dope PHP package repository. If you'r lucky to be attending this year's ZendCon, I recommend to catch Matthew Weier O'Phinney's session about Creating PHPantastic packages. I definitely be waiting for the related slides.

Happy packaging.