objects namespaces and application architecture
Traits
A trait is a way to reuse methods inside classes without inheritance. A class imports a trait with use, and the trait's methods become part of the class.
Traits are useful for small, focused behaviour shared by unrelated classes. They become risky when they hide dependencies, add lots of state, or turn into a substitute for proper composition.
Use A Trait For Small Shared Behaviour
<?php
declare(strict_types=1);
trait FormatsMoney
{
public function formatPennies(int $pennies): string
{
return '£' . number_format($pennies / 100, 2);
}
}
class InvoicePresenter
{
use FormatsMoney;
}
$presenter = new InvoicePresenter();
echo $presenter->formatPennies(2499) . PHP_EOL;
// Prints:
// £24.99
The trait is small and has no hidden state, so it is easy to understand.
Traits Are Copied Into The Class Shape
When a class uses a trait, calling code just sees a method on the class.
<?php
declare(strict_types=1);
trait HasReference
{
public function referenceFromId(int $id): string
{
return 'REF-' . str_pad((string) $id, 6, '0', STR_PAD_LEFT);
}
}
class OrderPresenter
{
use HasReference;
}
echo (new OrderPresenter())->referenceFromId(42) . PHP_EOL;
// Prints:
// REF-000042
The object does not expose where the method came from. That is convenient, but it can make large classes harder to trace.
Traits Can Require Methods
A trait can call methods that the using class must provide.
<?php
declare(strict_types=1);
trait HasPublicLabel
{
abstract public function name(): string;
public function publicLabel(): string
{
return strtoupper($this->name());
}
}
class Product
{
use HasPublicLabel;
public function name(): string
{
return 'Keyboard';
}
}
echo (new Product())->publicLabel() . PHP_EOL;
// Prints:
// KEYBOARD
This can be useful, but it creates a hidden contract. Keep it obvious and documented through names.
Resolve Method Conflicts Explicitly
If two traits provide the same method, PHP needs to know which one to use.
<?php
declare(strict_types=1);
trait ShortLabel
{
public function label(): string
{
return 'Short';
}
}
trait LongLabel
{
public function label(): string
{
return 'Long label';
}
}
class LabelPresenter
{
use ShortLabel;
use LongLabel {
LongLabel::label insteadof ShortLabel;
}
}
echo (new LabelPresenter())->label() . PHP_EOL;
// Prints:
// Long label
Conflicts are a sign to check whether the class is gathering too much unrelated behaviour.
Prefer Composition For Real Services
If the shared behaviour needs configuration, a database, a logger, a clock, or an API client, prefer a normal object dependency.
<?php
declare(strict_types=1);
final class MoneyFormatter
{
public function formatPennies(int $pennies): string
{
return '£' . number_format($pennies / 100, 2);
}
}
final class ReceiptPresenter
{
public function __construct(private MoneyFormatter $formatter)
{
}
public function total(int $pennies): string
{
return $this->formatter->formatPennies($pennies);
}
}
echo (new ReceiptPresenter(new MoneyFormatter()))->total(1299) . PHP_EOL;
// Prints:
// £12.99
Composition is easier to test and replace when behaviour has real dependencies.
What To Remember
Traits reuse code horizontally across classes. Keep them small, focused, and low-state. Avoid hiding service dependencies inside traits. When behaviour is important enough to configure, test, or replace, use composition instead.
Practice
Task: Share Formatting Behaviour
Create a small trait for formatting money.
Requirements
- Use
declare(strict_types=1);. - Create a
FormatsMoneytrait. - Add a method that formats integer pennies as pounds.
- Use the trait in an
InvoicePresenterclass. - Use the same trait in a
ReceiptPresenterclass. - Print one formatted value from each presenter.
- Include the expected output as comments in the same PHP code block.
The trait should stay small and stateless.
Show solution
<?php
declare(strict_types=1);
trait FormatsMoney
{
public function formatPennies(int $pennies): string
{
return '£' . number_format($pennies / 100, 2);
}
}
class InvoicePresenter
{
use FormatsMoney;
}
class ReceiptPresenter
{
use FormatsMoney;
}
echo (new InvoicePresenter())->formatPennies(129950) . PHP_EOL;
echo (new ReceiptPresenter())->formatPennies(2499) . PHP_EOL;
// Prints:
// £1,299.50
// £24.99
The trait is small, focused, and stateless. If formatting needed locale configuration or a currency service, a normal injected object would be a better design.