Telekom Baskets Bonn

Relaunch 2023/2024

Timetable

  • Baskets Relaunch: some details
  • Relaunch Recap: lessons learned
  • Stats Stats Stats: an API overview
  • S.O.L.I.D: design principles in OOP
  • API Deep Dive: refactoring and optimization

Baskets Relaunch

Some details

Design
Feedback

anyone?

PM Feedback?

TYPO3 + Content Blocks

Content Blocks

  • Content Elements
  • Record Types
  • Page Types

Content Elements in 2023

  • TCA configuration
  • TSConfig
  • TypoScript
  • New Content Element Wizard
  • Icon registration
  • Data processors
  • Laborious backend previews
  • Additional fields:
  • ext_tables.sql
  • More TCA
  • Labels
  • More view helpers and/or data processors

Content Elements with Content Blocks

  • YAML-Configuration only
  • Content Elements are (mostly) encapsulated in separate directories
  • Easy to implement backend preview
  • Extremely powerful data processor
name: faktore/cta-banner
title: CTA Banner
description: 'A banner with selectable BG color and RTE text editor'
group: common
prefixFields: true
prefixType: vendor
fields:
  - identifier: background
    type: Select
    renderType: selectSingle
    items:
      - label: 'Magenta (Standard)'
        value: bg-brand-primary
      - label: 'Turquoise '
        value: bg-brand-secondary
  - identifier: bodytext
    useExistingField: true
    enableRichtext: true

Record Types

  • Same concept but for own database tables
  • Same YAML-Structure
  • Configuration in one place
  • Easy to implement backend preview
  • Extremely powerful data processor

Page types

  • Same concept
  • Minimal configuration
  • Custom page property
    fields and palettes with ease

easyCredit BBL API

details later...

Lessons Learned

Lessons
learned

Design edition

Lessons learned

Project management edition

Design
vs

detailed concept

or

requirement specification

Tickets Tickets Tickets

Do your Research


(even paid)

RTFM but do not trust it.

Trust no one and especially not the manual.

About architecture and practicality

ToDo markers
are fine.

When there's time
to tackle them.

ToDo markers /
refactoring requires
TestsTestsTests

More Lessons learned?

Stats
Stats
Stats

Data structures Source and Target

About Players, Season Players and changing UUIDs

Unique UUIDs that are not unique on first sight

Endpoints: Games, Players Stats, Game Stats, Standings, Live Scores

Depend dependencies and caches to improve caches

Sneak peek:
Software Architecture

The Pipeline
Pattern

The Pipeline Pattern

aka. Pipes-and-Filters-Pattern
  • Mostly used to process data streams
  • Breaks down complex logic into multiple small steps
  • A pipeline defines the steps to be performed and their sequence
  • Each step takes data from the previous step for further processing
  • Each step provides the result of the processed data for the next step
  • Steps can be reused (even across pipelines)

Games Pipeline

<?php
  protected static array $gamesSteps = [
      GetGames::class,
      GetTeams::class,
      GetSeasons::class,
      BuildGamesDataHandlerArray::class,
      BuildTeamsDataHandlerArray::class,
      BuildSeasonTeamsDataHandlerArray::class,
      BuildSeasonsDataHandlerArray::class,
      GetTeamLogos::class,
      SetGameSeasonTeamRelations::class,
      SetGameSeasonRelations::class,
      SetSeasonTeamSeasonRelations::class,
      SetSeasonTeamTeamRelations::class,
      ImportData::class
  ];

Standing Pipeline

<?php
  protected static array $standingsPipeline = [
      GetStandings::class,
      GetTeams::class,
      GetTeamLogos::class,
      GetSeasons::class,
      BuildSeasonsDataHandlerArray::class,
      BuildStandingsDataHandlerArray::class,
      BuildTeamsDataHandlerArray::class,
      BuildSeasonTeamsDataHandlerArray::class,
      SetStandingSeasonRelations::class,
      SetStandingSeasonTeamRelations::class,
      SetSeasonTeamTeamRelations::class,
      ImportData::class
  ];

End
of
part
one

SOLID

The first 5 principles of
Object Oriented Design

May 13th, 2024 / Faktor E GmbH

  • S Single-Responsibility Principle
  • O Open-Closed Principle
  • L Liskov Substitution Principle
  • I Interface Segregation Principle
  • D Dependency Inversion Principle

Single-Responsibility Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

SRP:
An example in PHP code

<?php
class BlogPost
{
    private Author $author;
    private string $title;
    private string $content;
    private \DateTime $date;

    // [...]

    public function getData(): array
    {
        return [
            'author' => $this->author->getFullName(),
            'title' => $this->getTitle,
            'content' => $this->getContent,
            'timestamp' => $this->date->getTimestamp()
        ];
    }

    public function printJson(): string
    {
        return json_encode($this->getData());
    }

    public function printHtml(): string
    {
        return '
[...]
'; } }
<?php
class BlogPost
{
    // [...]

    public function getData(): array
    {
        return [
            'author' => $this->author->getFullName(),
            'title' => $this->getTitle,
            'content' => $this->getContent,
            'timestamp' => $this->date->getTimestamp()
        ];
    }
}

interface PrintableBlogPost
{
    public function print(BlogPost $blogPost);
}

class JsonBlogPostPrinter implements PrintableBlogPost
{
    public function print(BlogPost $blogPost): string
    {
        return json_encode($blogPost->getData());
    }
}

class HtmlBlogPostPrinter implements PrintableBlogPost
{
    public function print(BlogPost $blogPost): string
    {
        return '
[...]
'; } }

Open-Closed Principle

Objects or entities should be open for extension but closed for modification.

OCP:
An example in PHP code

Presumption

<?php

class Square
{
    public int $length;

    public function construct($int length): void
    {
        $this->length = $length;
    }
}

class Circle
{
    public int $radius;

    public function construct(int $radius): void
    {
        $this->radius = $radius;
    }
}

<?php

class Rectangle
{
    public int $height;
    public int $width;

    public function construct(int $height, int $width): void
    {
        $this->height = $height;
        $this->width = $width;
    }
}

class Triangle
{
	// [...]
}




<?php

class AreaCalculator
{
    protected $shapes;

    public function __construct(array $shapes = [])
    {
        $this->shapes = $shapes;
    }

    public function sum(): int
    {
        foreach ($this->shapes as $shape) {
            if ($shape instanceof Square::class)) {
                $area[] = pow($shape->length, 2);
            } elseif ($shape instanceof Circle::class) {
                $area[] = pi() * pow($shape->radius, 2);
            } elseif ($shape instanceof Rectangle::class) {
                $area[] = $shape->height * $shape->width;
            } // [...]
        }

        return array_sum($area);
    }
}






<?php
interface ShapeInterface
{
    public function area(): int;
}

class Square implements ShapeInterface { // [...] }
class Circle implements ShapeInterface { // [...] }

class Square
{
    // [...]

    public function area(): int
    {
        return pow($this->length, 2);
    }
}

class AreaCalculator
{
    // [...]

    public function sum(): int
    {
        foreach ($this->shapes as $shape) {
            $area[] = $shape->area();
        }

        return array_sum($area);
    }
}

Liskov Substitution Principle

If it looks like a duck, swims like a duck,
quacks like a duck, but needs batteries

you probably have the wrong abstraction.

  • Barbara Liskov
  • * November 7, 1939 (age 84)
    LA, California, USA
  • Institute Professor and
    Ford Professor of
    Engineering at MIT

Let q(x) be a property provable about objects of x of type T.
Then q(y) should be provable for objects y of type S where S is a subtype of T.

Every subclass or derived class should be substitutable for their base or parent class.

LSP:
An example in PHP code

<?php
class Rectangle
{
    protected int $width;
    protected int $height;

    public function setWidth(int $width): void
    {
        $this->width = $width;
    }

    public function setHeight(int $height): void
    {
        $this->height = $height;
    }

    public function calculateArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $width;
        $this->height = $width;
    }

    public function setHeight(int $height): void
    {
        $this->width = $height;
        $this->height = $height;
    }
}

<?php
class Rectangle implements CalculableArea
{
    protected int $width;
    protected int $height;

    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function calculateArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square implements CalculableArea
{
    protected int $edge;

    public function __construct(int $edge)
    {
        $this->edge = $edge;
    }

    public function calculateArea(): int
    {
        return $this->edge ** 2;
    }
}

interface CalculableArea
{
    public function calculateArea(): int;
}

Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use.

LSP:
An example in PHP code

<?php

interface Exportable
{
    public function getPdf();
    public function getCsv();
}

class Invoice implements Exportable
{
    public function getPdf()
    {
        // ...
    }
    public function getCsv()
    {
        // ...
    }
}

class CreditNote implements Exportable
{
    public function getPdf()
    {
        //...
    }

    public function getCsv()
    {
        throw new \NotUsedFeatureException();
    }
}
<?php
interface ExportablePdf
{
    public function getPdf();
}

interface ExportableCsv
{
    public function getCsv();
}

class Invoice implements ExportablePdf, ExportableCsv
{
    public function getPdf()
    {
        // ...
    }
    public function getCsv()
    {
        // ...
    }
}

class CreditNote implements ExportablePdf
{
    public function getPdf()
    {
		//...
    }
}


Dependency
Inversion
Principle

Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.

<?php
class MySQLConnection
{
    public function connect()
    {
        return 'Database connection';
    }
}

class PersistenceService
{
    public function __construct(
        private readonly MySQLConnection $dbConnection
    ) {}
}
<?php
class SqLiteConnection
{
    public function connect()
    {
        return 'Database connection';
    }
}
<?php
interface DbConnectionInterface
{
    public function connect();
}

class MySQLConnection implements DbConnectionInterface
{
    public function connect()
    {
        return 'Database connection';
    }
}

class SqLiteConnection implements DbConnectionInterface
{
    public function connect()
    {
        return 'Database connection';
    }
}

class PersistenceService
{
    public function __construct(
        private readonly DbConnectionInterface $dbConnection
    ) {}
}

The
Gang
Of
Four

#

Design Pattern Types

Creational Patterns

Singleton Pattern

Structural Patterns

Adapter Pattern

Behavior Patterns

Observer Pattern

Patterns today

  • Adapter Pattern
  • Builder Pattern
  • Factory Pattern
  • Resolver Pattern

Adapter pattern

The Adapter pattern is a design pattern that allows objects with incompatible interfaces to work together.

It's part of the structural patterns defined by the Gang of Four.

namespace Faktore\Sitepackage\Http\Adapter;

use Faktore\Sitepackage\Http\ApiRequest;
use Psr\Http\Message\ResponseInterface;

interface ApiAdapterInterface
{
    public function sendRequest(
        ApiRequest $request
    ): ResponseInterface;
}
namespace Faktore\Sitepackage\Http;

use Faktore\Sitepackage\Exception;
use Faktore\Sitepackage\Http\Adapter\ApiAdapterInterface;
use Psr\Http\Message\ResponseInterface;

class ApiClient
{
    public function __construct(
        protected readonly ApiAdapterInterface $apiAdapter
    ) {}

    /**
     * @throws Exception
     */
    public function sendApiRequest(
        ApiRequest $apiRequest
    ): ApiResponse
    {
        $response = $this->apiAdapter
            ->sendRequest($apiRequest);
        return $this->processResponse($response);
    }

	// [...]
}
namespace Faktore\Sitepackage\Http\Adapter;

use Faktore\Sitepackage\Http\ApiRequest;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class GuzzleApiAdapter implements ApiAdapterInterface
{
    protected Client $httpClient;

    public function __construct()
    {
        $this->httpClient = GeneralUtility::makeInstance(Client::class);
    }

    /**
     * @throws GuzzleException
     */
    public function sendRequest(ApiRequest $request): ResponseInterface
    {
        $url = $request->getUri();
        $method = $request->getMethod();
        $headers = $request->getHeaders();

        return $this->httpClient->request(
            $method, $url, ['headers' => $headers]
        );
    }
}

Builder pattern

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

It's part of the creational patterns defined by the Gang of Four.

namespace Faktore\Sitepackage\Http\Builder;

use Faktore\Sitepackage\Exception;
use Faktore\Sitepackage\Http\ApiRequest;

class ApiRequestBuilder implements ApiRequestBuilderInterface
{
    protected ?string $url = null;
    protected ?string $method = null;
    protected array $headers = [];

    public function setUrl(string $url): self
    {
        $this->url = $url;
        return $this;
    }

    // [...]

    /**
    * @throws Exception
    */
    public function build(): ApiRequest
    {
        if (empty($this->url) || empty($this->method)) {
            throw new Exception(
                'URL and method must be set before building the ApiRequest.',
                1708543432
            );
        }

        return new ApiRequest(
            uri: $this->url,
            method: $this->method,
            headers: $this->headers
        );
    }
}

Factory pattern

A factory is an object for creating objects.

The logic which object type should be instantiated is encapsulated in such factory class.

Or should I say factory method?

Creational patterns of the Gang of Four


Factory Method
vs.
Abstract Factory

The Factory Method pattern (mostly) relies on inheritance
where a (abstract) base class implements a method to create instances of objects. The method might be overridden in derived classes.

The Abstract Factory pattern relies on composition. It provides a way to create families of related or dependent objects by encapsulating a group of individual factories without specifying their concrete classes.

Simple factory or programming idiom?

My personal opinion!

Use a separate class for the factory.

Both, instances of classes and static methods are fine.

A common interface for all possible concrete objects is necessary.

<?php
declare(strict_types=1);

namespace Faktore\Sitepackage\Pipeline;

use Faktore\Sitepackage\Domain\Configuration\ConfigurationCollectionInterface;
use Faktore\Sitepackage\Exception;
use ReflectionClass;
use TYPO3\CMS\Core\Utility\GeneralUtility;

class PipelineFactory
{
    /**
     * @param string $type
     * @param ConfigurationCollectionInterface $configurations
     * @return PipelineInterface
     * @throws Exception
     */
    public static function get(
        string $type,
        ConfigurationCollectionInterface $configurations
    ): PipelineInterface
    {
        $className = GeneralUtility::underscoredToUpperCamelCase($type) . 'Pipeline';
        $fullyQualifiedClassName = 'Faktore\\Sitepackage\\Pipeline\\' . $className;

        if(class_exists($fullyQualifiedClassName)) {
            $reflection = new ReflectionClass($fullyQualifiedClassName);
            if(!$reflection->implementsInterface(PipelineInterface::class)) {
                throw new Exception('Class needs to implement the PipelineInterface.', 1707941729);
            }
            return GeneralUtility::makeInstance($fullyQualifiedClassName, $configurations);
        }

        throw new Exception("Class with name `$className` does not exist.", 1707941730);
    }

}

(Service) Resolver pattern

The (Service) Resolver pattern provides a solution to resolve the appropriate service instances when multiple implementations of the same interface are registered with the dependency injection container.

I'm not part of the GoF Design Patterns. :(

class ParameterResolver
{
    protected ?ArrayIterator $parameterProviders = null;

    public function __construct(iterable $parameterProviders)
    {
        // Sorting by priority and some Symfony related stuff
    }

    /**
     * @throws Exception
     */
    public function resolve(string $apiName, string $parameterName): ParameterProviderInterface
    {

        foreach ($this->parameterProviders as $provider) {
            $concreteProvider = clone $provider;

            if ($concreteProvider->supports($apiName, $parameterName)) {
                $concreteProvider->setParameterName($parameterName);

                return $concreteProvider;
            }

            $concreteProvider = null;
            unset($concreteProvider);
        }

        throw new Exception(
            'No provider found to handle parameter with the name ' . $parameterName, 1713358720
        );
    }

    // ResolveMultiple()
}

API
Deep
Dive