DEV Community

david duymelinck
david duymelinck

Posted on

PHP fun: class functions

Yes you read that right, class functions instead of class methods. This is not a naming mistake.

The OOP way

class Product
{
    public function __construct(
        public string $name = '',
        public string $description = '',
        public Price $price = new Price(),
    )
    {}

    public function priceDisplay(ConcatenateString $string = ConcatenateString::SingleSpace): string
    {
        return  $this->price->amount . $string->value . $this->price->currency->value;
    }
}

class Price
{
    public function __construct(
        public float $amount = 0,
        public Currency  $currency = Currency::EUR,
    )
    {}
}

enum ConcatenateString: string
{
    case SingleSpace = ' ';
    case Comma = ',';
}

enum Currency: string
{
    case EUR = '€';
    case USD = '$';
}
Enter fullscreen mode Exit fullscreen mode

The focus is on displaying the product price.
If I take a very strict view of the single responsibility principle the priceDisplay method already violates it, because Product is a data object.

The procedural way

I can create a function to concatenate all strings.

function concatenate(ConcatenateString $string, string ...$values): string
{
    return implode($string->value, $values);
}
Enter fullscreen mode Exit fullscreen mode

The main problem is that it requires a lot more typing.

$product = new Product();
// OOP
echo $product->priceDisplay();
// procedural
echo concatenate(ConcatenateString::SingleSpace, $product->price->amount, $product->price->currency->value);
Enter fullscreen mode Exit fullscreen mode

Class functions

I'm not strictly breaking the one file one class OOP rule, so lets add a function to the class file.

// product.php 
class Product
{
    public function __construct(
        public string $name = '',
        public string $description = '',
        public Price $price = new Price(),
    )
    {}
}

function productPriceDisplay(Product $product): string
{
    return concatenate(ConcatenateString::SingleSpace, $product->price->amount, $product->price->currency->value);
}
Enter fullscreen mode Exit fullscreen mode

This reduces the typing and uses the concatenate function.

$product = new Product();

echo productPriceDisplay($product);
Enter fullscreen mode Exit fullscreen mode

If I can use the pipe operator, it will even be shorter.

echo new Product() |> productPriceDisplay(...);
Enter fullscreen mode Exit fullscreen mode

The only methods in Product would do data manipulation, like

class product 
{
  // ...
  public function setUSDPrice(float $amount) : void
  {
     $this->price = new Price($amount, Currency::USD);
  }
}

// in application
$product = new Product();

$product->price = new Price(0, Currency::USD);
$product->setUSDPrice(0);
Enter fullscreen mode Exit fullscreen mode

Conclusion

Class functions will look strange coming from OOP, but the more I think about composition the more it makes sense.
It has been on my mind for a while to give functions a bigger role than just helpers (like concatenate).
And after seeing a Brandon Rhodes talk I feel that I'm going in the right direction.

The rules I would set for the class functions are:

  • They should be prefixed with the class name
  • They should have the class instance as the first argument

The two things I'm missing from OOP in class functions are method visibility and method enforcement by contract.
I need a bigger project to experience if those things are going to be missed or not.

Top comments (1)

Collapse
 
andriy_ovcharov_312ead391 profile image
Andriy Ovcharov

Interesting. Thanks for sharing!