Code guidelines

Here you’ll find the code guidelines which you need to respect when developing at CollectiShop.

Functions

<?php
// Every function has a clear name, type hints and return types
function randomFunction(int $parameter): float
{
    return round(($parameter / 100), 2);
}

Returning

<?php
// A function returns as quick as possible
function validateField(Field $field): bool
{
    if (empty($field->getName())) {
        return false;
    }

    if ($field->getType() === "email" && !filter_var($field->getType(), FILTER_VALIDATE_EMAIL)) {
        return false;
    }
    return $field->isValid();
}

One responsability

Example of bad code

<?php
// Every function has its own responsability, so this is a bad
// example
function getPrice(float $price, bool $format = false): float|string
{
    if ($format && floor($price) === $price) {
        return "\$ "  . number_format($price, 0) . ".-";
    } elseif ($format) {
        return "\$ "  . number_format($price, 2);
    }
    return $price;
}

Example of good code

<?php
// Every function has its own responsability, so this is a good
// example
function getPrice(): float
{
    return $this->price;
}

function getFormattedPrice(): string
{
    if (floor($price) === $price) {
        return "\$ "  . number_format($price, 0) . ".-";
    }
    return "\$ "  . number_format($price, 2);
}

Use of constants

Bad example

<?php

class Foo extends Bar
{
    public function isRemovable(): bool
    {
        if (in_array($this->getId(), [1,2,3])) {
            return false;
        }
        return parent::isRemovable();
    }
} 

Good example

<?php

class Foo extends Bar
{
    public const FIRSTPAGE_ID = 1;
    public const SECONDPAGE_ID = 2;
    public const THIRDPAGE_ID = 3;
    
    public const PAGEID_LIST = [
      self::FIRSTPAGE_ID,
      self::SECONDPAGE_ID,
      self::THIRDPAGE_ID
    ];

    public function isRemovable(): bool
    {
        if (in_array($this->getId(), self::PAGEID_LIST)) {
            return false;
        }
        return parent::isRemovable();
    }
} 

Avoid (unnecessary) “else”

Bad example

<?php

class Foo extends Bar
{
    public function validateData(array $data): bool
    {
        if (empty($data)) {
            $isValid = false;
        } else {
            $isValid = true;
        }
        
        if (!isset($data["success"]) || $data["success"] === false) {
            $isValid = false;
            $success = false;
        } else {
            $success = true;
        }
        return ($isValid && $success);
    }
}

Good example

<?php

class Foo extends Bar
{
    public function validateData(array $data): bool
    {
        $isValid = false;
        $success = false;
        
        if (!empty($data)) {
            $isValid = true;
        }
        
        if (isset($data["success"]) && $data["success"] === true) {
            $success = true;
        }
        return ($isValid && $success);
    }
}

Avoid (unnecessary) multilevel if-statements

Bad example

<?php

class Foo extends Bar
{
    public function validateAddress(array $address): bool
    {
        $isValidAddress = false;
        if (!empty($address)) {
            if (!empty($address["zipcode"])) {
                if (!empty($address["housenumber"])) {
                    $isValidAddress = true;
                }
            } else {
                if (!empty($address["addressline1"])) {
                    if (!empty($address["addressline2"])) {
                        $isValidAddress = true;
                    }
                }
            }
        }
        return $isValidAddress;
    }
}

Good example

<?php

class Foo extends Bar
{
    public function validateAddress(array $address): bool
    {
        if (empty($address)) {
          return false;
        }
            
        if (empty($address["zipcode"]) || empty($address["housenumber"])) {
            return $this->validateAddressByAddressLines($address);
        }
        return true;
    }
    
    protected function validateAddressByAddressLines(array $address): bool
    {
        if (empty($address)) {
            return false;
        }
        
        if (empty($address["addressline1"]) || empty($address["addressline2"])) {
            return false;
        }
        return true;
    }
}

Make use of correct comparison operators

<?php

class RaceCar extends Car
{
    public function getInfo(): string
    {
        return "I'm a racecar";
    }
}

class Car extends Vehicle
{
    public function getInfo(): string
    {
        return "I'm a car";
    }

    public function setWheels(int $wheels): Car
    {
        if ($wheels >= 4) {
            $this->wheels = $wheels;
        }
        throw new VehicleWheelsException("A car has at least 4 wheels");
    }
    
    public function setPerformance(int $performance): Car
    {
        return match ($performance <=> 600) {
            -1 => $this,
            0 => $this,
            1 => new RaceCar(),
        }
    }
    
    public function getAverageMilesDriven(int ...$milesDriven): int
    {
        return (array_sum($milesDriven) / count($milesDriven));
    }
    
    public function getMilesDriven(): int
    {
        return $this->milesDriven ?? 0;
    }
    
    public function getDistanceDriven(): int
    {
        return ($this->milesDriven ?: ($this->kilometersDriven ?? 0));
    }
}

Preference for passing objects above plain data types

Bad example

<?php

class Foo extends Bar
{
    public function __construct(int $id, string $title, string $slug)
    {
        ...
    }
}

$newsArticle = new NewsArticle();
$foo = new Foo(
    $newsArticle->getId(), 
    $newsArticle->getTitle(), 
    $newsArticle->getSlug()
);

Good example

<?php

class Foo extends Bar
{
    public function __construct(NewsArticle $newsArticle)
    {
        ...
    }
}

$newsArticle = new NewsArticle();
$foo = new Foo($newsArticle);

Even better example

<?php

interface NewsArticleInterface
{
    public function getId(): int;
    public function getTitle(): string;
    public function getSlug(): string;
}

class Foo extends Bar
{
    public function __construct(NewsArticleInterface $newsArticle)
    {
        ...
    }
}

$newsArticle = new NewsArticle();
$foo = new Foo($newsArticle);

Use helper objects instead of global variables

Bad example

<?php

$title = (string)($_POST["title"] ?? "");
$referer = (string)($_SESSION["referer"] ?? "");
$search = (string)($_REQUEST["search"] ?? "");

Good example

<?php

$title = Request::getPost("title");
$referer = Session::get("referer");
$search = Request::getRequest("search");

Use an ORM instead of plain queries

<?php

public function getNewsArticlesBySearch(string $query): array
{
    $queryBuilder = new QueryBuilder();
    $queryBuilder->select(["id", "title", "slug"])
        ->from(NewsArticle::table)
        ->where("title LIKE ?", "%" . $query . "%");
    return $queryBuilder->fetchResults(NewsArticle::class);
}

Keep functions small

Bad example

<?php

class Order
{
    public function getTotal(): float
    {
        $total = 0.00;
        foreach ($this->getOrderLines() as $orderLine) {
            $total += ($orderLine->getPrice() * $orderLine->getAmount());
        }
        
        foreach ($this-getShippingCosts() as $shippingCost) {
            $total += $shippingCost->getPrice();
        }
        
        foreach ($this->getPaymentCosts() as $paymentCost) {
            $total += $paymentCost->getPrice();
        }
        
        foreach ($this-getDiscounts() as $discount) {
            if ($discount->hasDiscountPercentage()) {
                $total -= 100 - (($total / 100) * $discount->getPercentage());
            } else {
                $total -= $discount->getPrice();
            }
        }
        
        $vatTotal = 0.00;
        foreach ($this->orderLines() as $orderLine) {
            $vatTotal += ($orderLines->getPrice() * $orderLines->getAmount()) * $orderLine->getVatPercentage();
        }
        
        foreach ($this-getShippingCosts() as $shippingCost) {
            $vatTotal += ($shippingCost->getPrice() * $shippingCost->getVatPercentage());
        }
        
        foreach ($this->getPaymentCosts() as $paymentCost) {
            $vatTotal += ($paymentCost->getPrice() * $paymentCost->getVatPercentage());
        }
        
        foreach ($this-getDiscounts() as $discount) {
            if ($discount->hasDiscountPercentage()) {
                $vatTotal -= (100 - (($total / 100) * $discount->getPercentage())) * $discount->getVatPercentage();
            } else {
                $vatTotal -= ($discount->getPrice() * $discount->getVatPercentage());
            }
        }
        
        $total += $vatTotal;
        
        return $total;
    }
}

Good example

<?php

class Order
{
    public function getSubTotal(): float
    {
        $subTotal = 0.00;
        foreach ($this->getOrderLines() as $orderLine) {
            $subTotal += $orderLine->getTotal();
        }
        return $subTotal;
    }
    
    public function getShippingTotal(): float
    {
        $total = 0.00;
        foreach ($this-getShippingCosts() as $shippingCost) {
            $total += $shippingCost->getTotal();
        }
        return $total;
    }
    
    public function getPaymentTotal(): float
    {
        $total = 0.00;
        foreach ($this-getPaymentCosts() as $paymentCost) {
            $total += $paymentCost->getTotal();
        }
        return $total;
    }
    
    public function getDiscountTotal(float $total): float
    {
        $total = 0.00;
        foreach ($this-getDiscounts() as $discount) {
            $total += $discount->getDiscountByTotal($total);
        }
        return $total;
    }
    
    public function getVatTotal(): float
    {
        $vatCalculator = new VatCalculator();
        $total = $vatCalculator->getSubTotalVat();
        $total += $vatCalculator->getShippingTotalVat();
        $total += $vatCalculator->getPaymentTotalVat();
        $total -= $vatCalculator->getDiscountTotalVat($total);
        return $total;
    }
    
    public function getTotal(): float
    {
        $total = $this->getSubTotal();
        $total += $this->getShippingTotal();
        $total += $this->getPaymentTotal();
        $total -= $this->getDiscountTotal($total);
        $total += $this->getVatTotal();
        return $total;
    }
}