Showing posts with label Best Practice. Show all posts
Showing posts with label Best Practice. Show all posts

Friday, 17 January 2025

Developing a PHP package, 2025 edition

Since my blog post about the anatomy of a dope PHP package repository a lot has changed in the PHP ecosystem and it's surrounding tools. Time to reflect these changes and provide, yet another, guide to develop a PHP package in 2025.

Idea to implementation

The hardest part to get started is finding an idea worth implementing. Sources of inspiration can be visiting conferences, visiting local user groups, or the good old blog post reading. Also, daily business might generate an idea worth investing time. The idea might be very niche at first but that shouldn't stop you from going for it; the learnings might advance your self-confidence and career.

Getting started

Template repositories are a GitHub feature released in June 2019, which allows you and others to generate new Git repositories with the same directory structure and files based on the given template repository. Some templates to look into are spatie/package-skeleton-php and ergebnis/php-package-template. If you're developing a dedicated Laravel package spatie/package-skeleton-laravel is a good starting point. For checking if a package is PDS conform, there are for one the command-line tools of the pds/skeleton project or the package analyser which is a very opinionated validator/analyser.

Must have development tools

Some of the above-mentioned package templates come with preconfigured development tools like PHPUnit or the PHP Coding Standards Fixer. Other tools you might have to add or switch yourself. For example when you're developing a Laravel package you might want to replace PHPUnit with the traction gaining Pest testing framework and the PHP Coding Standards Fixer with Pint. For aiding you in development, tools like PHPStan and Rector have become quite mandatory. Also, worth checking out is their thriving extension ecosystem e.g. Larastan and rector-laravel. For the CI part, GitHub Actions have replaced former CI environments like Travis CI. So it might be a good invest to look into the usage and definition of GitHub Actions.

The automation of dependency updates for dependant Composer packages can currently be handled via Dependabot but might soon be replaced by Conductor.

Identifying wording or grammar mistakes in repositories might become easier once we can add Peck, a wrapper around GNU Aspell, into the mix.

To practice Datensparsamkeit you can add the lean-package-validator, which ensures that no unnecessary package artifacts end up in the distributed dist archives. For some additional development tools, it's also worth checking out Tomas Votruba's tools selection.

Filling the gaps with polyfills

In case you're stuck with an older PHP or PHPUnit version, there are polyfills (polyfill-php83, polyfill-php84, and PHPUnit-Polyfills) available for filling the gaps; which eases later migrations to current PHP or PHPUnit versions.

Registering your package

After having finished the implementation of the package you need to Git tag it, ideally following semantic versioning, and submit it at Packagist, the main PHP package repository.

Package promotion

To promote your package start with a care- and heartful crafted README.md file. If you want to rise the visibility of your package invest some time to create a catching logo or delegate its creation to your bubble. Other channels to promote your work are X, the awesome PHP list, and giving a presentation about it at your local user group.

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.

Monday, 28 July 2008

Creating custom PHPUnit assertions

While developing PHP applications and applying developer testing the applications safety net will grow along the timeline, and as normal code, test code should be in a fresh, odour free state too. A common test code smell, amongst others, is the duplication of assertion logic which can reduce reusability, readability and thereby obscure the specific verification intention of tests. To subdue this special smell several patterns and refactorings are available to acquaint the test code with the DRY principle. So in this blog post I'd like to set the focus on some of the aspects of the Custom Assertion pattern, by showing how to create custom PHPUnit assertions, which attacks the above mentioned smell and its retroactive effects with a huge antiperspirant flagon, while also providing the chance to build a customer friendly and domain related test vocabulary.

The first introductive code snippet shows an example of unwanted code duplications and smells in a PHPUnit(i.e. version: 3.2.21) test case class spreading over several test methods. The first test code duplications smell is present when verifying that a given bag is having an expected item count and an explicit 'intent obscuration' smell can be spotted when verifying the bags stock id against an assumed convention via a 'distracting' regular expression.

<?php
require_once 'PHPUnit/Framework.php';
require_once 'Record/Bag.php';
require_once 'Record/Item.php';

class Record_Bag_Test extends PHPUnit_Framework_TestCase
{
private $_bag = null;

protected function setUp()
{
/**
* Creates a new named bag with an unique stock id
* in a format of AAA-NN-AA-NNNNNNNN e.g. XDS-76-YS-00000124,
* where A stands for an alphanumeric and N for a numeric character.
*/
$this->_bag = new Record_Bag('Test_Bag');
}

/**
* @test
*/
public function bagShouldNotContainDuplicateItems()
{
$this->assertTrue(0 === $this->_bag->getItemQuantity(),
'New bag not empty on creation.');

$this->_bag->addItem(new Record_Item('Dubplate 1'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));

$this->assertTrue(2 === $this->_bag->getItemQuantity(),
'Bag does contain duplicated items.');
}

/**
* @test
*/
public function bagShouldBeReducedByOneItemAfterRemoval()
{
$this->assertTrue(0 === $this->_bag->getItemQuantity(),
'New bag not empty on creation.');

$this->_bag->addItem(new Record_Item('Dubplate 1'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));
$this->_bag->addItem(new Record_Item('Dubplate 3'));

$this->_bag->removeItem('Dubplate 2');

$this->assertTrue(2 === $this->_bag->getItemQuantity(),
'Former three items bag does not contain two items after removal of one.');
}

/**
* @test
*/
public function bagStockIdShouldFollowAgreedConvention()
{
$stockIdPattern = '/^[A-Z]{3}-\d{2}-[A-Z]{2}-\d{8}/U';
$this->assertRegExp($stockIdPattern, $this->_bag->getStockId(),
'Stock id <string:' . $this->_bag->getStockId()
. '> does not follow the agreed convention.');
}

....

protected function tearDown()
{
unset($this->_bag);
}
}

Mechanics of rolling custom assertions

The 'Custom Assertion' pattern can be applied from the very first test code creation activities, in sense of a prefactoring, or refactored towards by extracting the assert duplications and verification intention obscurations into tailormade and intent revealing named assertions.

To define custom assertions are several approaches available, the first and easiest one is to define custom assertions merely for a single test class and make them private (inline) methods of this specific class by facading/wrapping the standard PHPUnit assertion. This approach is outlined in code snippet a).

Another approach is to create/introduce an Assert Class to promote a cleaner reusability in other test case classes or scenarios, this approach might get chosen to collect and organize the evolving domain specific assertions. You will see a code sketch of this approach in code listing b).

Other and more complex approaches would be the utilisation of PHPUnits' Constraint feature which is available since release 3.0.0 or in the near future the use of the Hamcrest test matcher features which might be available from PHPUnit 4.0.0.

Either way the purpose specific assertions should further provide a default and convention conform assertion message, which will be raised on a verification failure, and also the feature to feed in a custom one to avoid gambling annoying Assertion Roulette rounds.

a) Assert definition inside Test Case:
<?php
require_once 'PHPUnit/Framework.php';
require_once 'Record/Bag.php';
require_once 'Record/Item.php';

class Record_Bag_Test extends PHPUnit_Framework_TestCase
{
private $_bag = null;

protected function setUp()
{
$this->_bag = new Record_Bag('Test_Bag');
}

....

private function assertBagItemCount($expectedCount, $bag,
$message = 'Expected bag item count <integer:#1> does not match actual count of <integer:#2> items.')
{
if (strpos($message, '#1')) {
$message = str_replace('#1', $expectedCount, $message);
}
if (strpos($message, '#2')) {
$message = str_replace('#2', $bag->getItemQuantity(), $message);
}
$this->assertTrue($expectedCount === $bag->getItemQuantity(), $message);
}
}

b) Assert definition in Assert Class:
<?php
require_once 'PHPUnit/Framework/Assert.php';

class Record_Bag_Assert extends PHPUnit_Framework_Assert
{
/**
* Verifies that a given bag stock id follows the agreed convention.
*
* @param string $stockId
* @param string $message
* @see Record_Bag::createUniqueStockId()
*/
public function assertStockIdFollowsConvention($stockId,
$message = 'Stock id <string:##> does not follow the agreed convention.')
{
if (strpos($message, '##')) {
$message = str_replace('##', $stockId, $message);
}
$stockIdPattern = '/^[A-Z]{3}-\d{2}-[A-Z]{2}-\d{8}/U';
$this->assertRegExp($stockIdPattern, $stockId, $message);
}

....

}

Putting the custom assertions to work

Now as the tailormade assertions are available all verification work can be delegated to them the same way it would be done with any of the standard PHPUnit assertions. In case the custom assertions are hosted in an 'Assert Class' they can be required_once or loaded via __autoload() in the test setup, otherwise if they are defined inside the test case class itself they can be used like regular private methods of that class. The last code extract illustrates the use of the prior outlined 'Assert Class' assertion for verifing the stock id format alongside the use of the 'inline' custom assertion for verifying the amount of items in a given bag.
<?php
require_once 'PHPUnit/Framework.php';
require_once 'Record/Bag/Assert.php';

class Record_Bag_Test extends PHPUnit_Framework_TestCase
{

....

/**
* @test
*/
public function bagShouldNotContainDuplicateItems()
{
$this->assertBagItemCount(0, $bag, 'New bag not empty on creation.');

$this->_bag->addItem(new Record_Item('Dubplate 1'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));

$this->assertBagItemCount(2, $bag, 'Bag does contain duplicate items.');
}

/**
* @test
*/
public function bagShouldBeReducedByOneItemAfterRemoval()
{
$this->assertBagItemCount(0, $bag, 'New bag not empty on creation.');

$this->_bag->addItem(new Record_Item('Dubplate 1'));
$this->_bag->addItem(new Record_Item('Dubplate 2'));
$this->_bag->addItem(new Record_Item('Dubplate 3'));

$this->_bag->removeItem('Dubplate 2');

$this->assertBagItemCount(2, $bag,
'Former three items bag does not contain two items after removal of one.');
}

/**
* @test
*/
public function bagStockIdShouldFollowAgreedConvention()
{
Record_Bag_Assert::assertStockIdFollowsConvention(
$this->_bag->getStockId());
}

....

}

Adding value through a domain specific test vocabulary

As you might have noticed the readability and intention communication of the test code has been improved significantly from the introductive code snippet towards the last one. Furthermore by distilling a ubiquitous(see Domain Driven Design book by Eric Evans) test language domain experts, which are mostly not fluent in the targeted programming language i.e. PHP, are enabled to read test code and provide valuable feedback or contribute changes to test scenarios affecting their domain.

Another common area where domain specific test vocabularies are used, by providing their own assertion and constraint sets, test case classes and additional test helpers, are extensions to xUnit frameworks e.g. DBUnit for PHPUnit or the Zend Framework MVC testing scaffold.

Friday, 4 July 2008

Six valuable Phing build file refactorings

Some weeks months ago I finally got my hands on the ThoughtWorks Anthology and got immediately hooked on one of the featured essays called 'Refactoring Ant Build Files' contributed by Julian Simpson aka the build doctor. After absorbing and studying the provided catalogue of overall 24 refactorings, I spent some time to transform a few health-promoting ones to the Phing universe. So the following post will outline six five basic, but valuable Phing build file refactorings by showing the smelly example first, followed by the scentless one and a closing refactoring description.

Making build files 'first-class' codebase citizens

You might ask yourself if there even is any need to refactor and care about mostly poorly treated project artifacts like build files. Well according to the book market there are growing needs and catalogues for refactoring non-sourcecode matters like databases and nowadays even (X)HTML, and as build tools are often used to automate the whole build and delivery process of complete projects, their feed build files should be readable and clear, painless maintainable and easily customizable as the controlled project takes new directions and hurdles.

Today build files are also often used to drive the Continuous Integration(CI) process and are heavily used to run local development builds prior to commiting the finished development tasks, therefor messy and clotty build files will have a counterproductive impact on the build management lifecycle and on making required changes to it. So when striving for codebase citizen equality agree upon a build file coding standard (e.g. 5. Introduce distinct target naming or a common element indentation), reside all build files of a project in a SCM system like Subversion or Git and try not to neglect the build files constantly just because they aren't actual business logic.

Trapeze balancing without a safety net

While developing an applications business logic 'ideally' automated behaviour verifying tests are written, whether in a test-first or test-last approach, and these are building the implicit and required safety net for all follow-up refactorings. For build files there are currently no tailored testing tools/frameworks available to warrant their external behaviour during and after a refactoring, though it might be possible to create a basic safety net by using for example a combination of PHPUnit's assertFileExists and assertContains assertions. And even if there were such tools available, it's rather questionable that these would be applied to test-drive mostly simple starting and incremental evolving build files. So currently this flavour of refactoring needs to be applied with much more descipline and caution than classic sourcecode refactorings, even 'old-school' manual tests have to be run frequently and only with a proceeding practice more and more agressive refactorings will become an everyday tool. After this short note of caution, let us jump right into the refactoring catalogue extract.

1. Extract target

Take parts of a large target, declare them as independent targets and preserve dependencies by using the
target depends attribute.
Before
<target name="what-a-clew">

<phplint>
<fileset dir="${build.src}">
<include name="**/*.php"/>
</fileset>
</phplint>

<phpcodesniffer standard="PEAR" format="summary">
<fileset dir="${build.src}">
<include name="**/*.php"/>
</fileset>
</phpcodesniffer>

<phpunit>
<formatter todir="reports" type="xml"/>
<batchtest>
<fileset dir="="${build.tests}">
<include name="**/*Test*.php"/>
</fileset>
</batchtest>
</phpunit>

</target>
After:
<target name="phplint-report">
<phplint>
....
</phplint>
</target>

<target name="sniff-report" depends="phplint-report">
<phpcodesniffer standard="PEAR" format="summary">
....
</phpcodesniffer>
</target>

<target name="test-report" depends="sniff-report">
<phpunit>
....
</phpunit>
</target>
The Extract target refactoring is similar to the well-known Extract method refactoring catalogued by Martin Fowler and should be applied to unclutter long targets, which can become hard to understand and troubleshoot while maintaining or extending a build file. This refactoring is achieved by taken each atomic task (e.g. phplint) of the cluttered target and provide them each a own target (e.g. phplint-report) while the former tasks execution sequence can be obtained by utilizing the target depends attribute. You can compare this refactoring to the technique of tackling a method that's to large and infringes upon the single responsibility principle.

While twiddling with this refactoring I came up with a follow-up and hand in hand refactoring that might go by the name of Introduce facade target, which simply orchestrates the target execution sequence so you can remove the depends attribute of all orchestrated targets and thereby use them separately if needed and advisable. The following build file extract shows the result of this refactoring in action.

1.1 Introduce facade target

Provide a facade target to obtain the task execution sequence and to make each involved target a single callable unit.
After:
<target name="phplint-report">
<phplint>
....
</phplint>
</target>

<target name="sniff-report" depends="phplint-report">
<phpcodesniffer standard="PEAR" format="summary">
....
</phpcodesniffer>
</target>

<target name="test-report" depends="sniff-report">
<phpunit>
....
</phpunit>
</target>

<target name="quality-report" depends="lint-report, sniff-report, test-report"
description="Generates the overall projects quality report" />

2. Introduce property file

Move infrequently changing properties from the build file body to a flat file.
Before:
<property name="db.port" value="3306" />
<property name="db.name" value="example" />
<property name="db.user" value="funkdoc" />
....
<property name="runtime.property.x" value="default" />
....
After:

build.poperties file
[example properties]
db.port = 3306
db.name = example
db.user = funkdoc
....

build file
<property file="build.properties" />
<property name="runtime.property.x" value="default" />
....
The Introduce property file refactoring can be applied for moving infrequently changing or static properties out of the main build file body to raise the overall legibility and keep them distinct from runtime properties. The downside of this refactoring is a lost of property visibility and breaking up the former single build file into multiple units, which is contradictory to the third ANT best practice named 'Prefer a Single Buildfile' of an older best practice catalogue compiled by Eric M. Burke. So in this case, like in any case, you have to make your own choice based on your needs and requirements.

3. Replace comment with description

Annotate elements(targets) with the description attribute instead of XML comments.
Before:
<!-- This target runs the PHP_Codesniffer task and reports coding standard violations --!>
<target name="sniff-report">
....
</target>
After:
<target name="sniff-report" description="Runs the PHP_Codesniffer task and reports coding standard violations">
....
</target>
Often build files are accentuated with plain XML comments to retain the mechanics and purpose of build file elements(i.e. targets) and can become a diversionary/obscuring source while maintaining or extending a build file. By using the available description attribute of the target element to annotate its purpose it's possible to reduce that kind of noise and even better, if used constantly, they can provide valuable information about all accumulated targets of a build file when phing is called with the -l(ist) option. As you can see the Replace comment with description refactoring requires a minimum of effort/investment to achieve a very valuable impact.

4. Reuse elements by id

Declare an instance e.g. a fileset once and make references to it elsewhere to reduce duplication and increase clarity.
Before:
<target name="phplint-report">
<phplint>
<fileset dir="${build.src}">
<include name="**/*.php"/>
</fileset>
</phplint>
</target>

<target name="sniff-report" depends="phplint-report">
<phpcodesniffer standard="PEAR" format="summary">
<fileset dir="${build.src}">
<include name="**/*.php"/>
</fileset>
</phpcodesniffer>
</target>
After:
<fileset id="src_artifacts" dir="${build.src}">
<include name="**/*.php"/>
</fileset>

<target name="phplint-report">
<phplint>
<fileset refid="src_artifacts" />
</phplint>
</target>

<target name="sniff-report" depends="phplint-report">
<phpcodesniffer standard="PEAR" format="summary">
<fileset refid="src_artifacts" />
</phpcodesniffer>
</target>
The Reuse elements by id refactoring is, as the short description states, tailor-made to increase clarity while reducing code duplication, which is when present a risk that an alternation made to one element will be skipped for the other duplicates, by declaring top-level elements once by assigning an id attribute to it and then referring to it thoughout the rest of the build file. This refactoring is best compared to the classic sourcecode refactoring called Pull Up Method also catalogued by Martin Fowler and moreover it enforces the compliance with the DRY principle by providing a single point of change for futurities alternations.

5. Introduce distinct target naming

Use a different punctuation for targets and properties to enhance readability.
Before:
<property name="example.property1" value="abc" />
<property name="example_property2" value="def" />
<property name="example-Property3" value="ghi" />

<target name="example.target1">
<echo msg="${example.property1}" />
....
</target>

<target name="example-target2">
<echo msg="${example-Property3}" />
....
</target>
After:
<property name="example.property1" value="abc" />
<property name="example.property2" value="def" />
<property name="example.property3" value="ghi" />

<target name="example-target1">
<echo msg="${example.property1}" />
....
</target>

<target name="example-target2">
<echo msg="${example.property3}" />
....
</target>
The Introduce distinct target naming refactoring once again tackles the improvement of readability in a build file by applying a constant and different punctuation on the common elements: targets and properties. The appliance of this refactoring leaves you and coworkers with an immediate reply whether you're looking at a property value or a target and can lead towards an agreed upon in-house/project build file coding standard. For target names underscores and dashes are suitable, although dashes are preferred by me as a hypen is used when enforcing internal targets, while for the build properties dots should be considered to build namespaces and their names should 'always' be lowercased except for environment variables/properties.

In case this blog post whetted your appetite for more, heavier and here uncovered build file refactorings, you might consider picking up a copy of the ThoughtWorks Anthology book. Last but not least a shout out goes to the build doctor for the remix permission and until the next post I'm ghost like dog.

Wednesday, 7 November 2007

Utilizing designer toys as an extreme feedback device

extreme feedback deviceIn response to Nick Halstead's Programming Tips Competition I dug a bit in my blog's idea queue for something related. The idea I picked includes some thoughts on how to gently prod the implementation of developer testing/specifying as an everyday team practice, assuming a head-nod/good-to-go from management. So today you won't see any code, as this is more related to the area of team building and process adoption. Instead of starting with the 'dictatorial' hammer and thereby possibly discouraging T2 developers or scaring away T3 developers, it would be wisely to use a fun and far more important attention creating approach. As this idea popped up on my mind I found a really suitable designer toy called Totem Doppelganger from Anton Ginzburg, consisting of three stackable look-a-like ghosts. I guess an excellent match to disenchant the initial 'mystics' of developer testing/specifying. As them are separable they can be used to accompany and indicate the ranged skill-development of each involved developer/team and thereby act as an extreme feedback device for both parties.

An example scenario could be:

At the beginning of process adoption every developer/team without or less previous knowledge might get two feedback devices(ghosts), which will be reduced to one on the path to the expected knowledge. One device might remain to signal the willingness to spread the gained knowledge to other struggling with adoption the practices or new team members, this tends to the direction of building a Developer Testing Master. In the case of not practicing pair-programming they can even act as a reminder to apply developer testing/specifying until it becomes a flesh-and-blood habit.

Monday, 2 April 2007

Best Practice #1: Keep location and naming comments in your templates

In projects with lots of nested templates I once again experienced that it's easier to get a grip on the overall structure if there are simple HTML comments present documenting where the template files are located and how they are named.

Simply by placing comments like <!-- [tmpl] /path/to/template/directory/template.php|tpl|phtml begin|end -->, or something similar, at the start and end of every template file this reduces maintenance and acquisition time for everybody involved, now and in the future. A simple view at the source of the rendered output quickly unfolds all involved templates and their location. This comes in very handy if your not familiar with the template structure of a project and have to expand the provided template data or solve some layout issues.

To avoid revealing parts of the projects directory structure you can strip these comments before deployment via the StripLineComments filter used in a filterchain of Phing, the PHP build tool, and keep them for internal use in the development trunk.