Combineren van classes

Overzicht Reageren

Sponsored by: Vacatures door Monsterboard

Pagina: 1 2 volgende »

Jorn Reed

Jorn Reed

08/12/2022 15:44:27
Quote Anchor link
Hoi, ik werk aan een project waarin ik een class heb gemaakt die api calls doet. Dit haalt producten op, maar kan ook producten/klanten etc opslaan.

Ook heb ik een Cart class gemaakt. Ik vind het handig om alle producten zo bij te houden en het totaal bedrag te bereken. De Class methodes worden uitgevoerd via ajax calls, dus de product ids komen vanuit de voorkant meegestuurd via JS. Nu is het zo dat ik nog het id, product naam en prijs meestuur naar de Cart. Maar wil eigenlijk alleen nog maar het id meesturen, om dat de gebruiker daar weinig mee kan saboteren. Ik weet wel hoe ik de data enzo kan ophalen, maar vind het slordig om direct die api call functies van die api class direct in mijn Cart class te gebruiken.

Heeft iemand ideeen, hoe ik dit netjes kan doen?
 
PHP hulp

PHP hulp

22/12/2024 10:18:14
 
Ozzie PHP

Ozzie PHP

08/12/2022 22:50:20
Quote Anchor link
Wellicht kun je de API class meegeven aan de Cart class?

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

class Cart {

  private object $api;

  public function __contruct(ApiClass $api) {
    $this->api = $api;
  }


  public function toonProducten() {
    $producten = $this->api->haalProductenOp();
    $this->toon($producten);
  }

}


?>

Hecht niet teveel waarde aan deze specifieke code. Het gaat alleen even om het idee.

>> ... een project waarin ik een class heb gemaakt die api calls doet. Dit haalt producten op, maar kan ook producten/klanten etc opslaan.

Als dit allemaal wordt gedaan door één en dezelfde class, dan zit er veel te veel functionaliteit in die class en moet je eigenlijk e.e.a. opsplitsen.
 
Ward van der Put
Moderator

Ward van der Put

09/12/2022 11:22:45
Quote Anchor link
Ozzie PHP op 08/12/2022 22:50:20:
Hecht niet teveel waarde aan deze specifieke code. Het gaat alleen even om het idee.

Dat idee overtreedt het Single-Responsibility Principle (SRP), de S in SOLID. Twee keer zelfs.

Als je de class Cart beschouwt als een container met references naar producten, dan is het vullen van die container een andere verantwoordelijkheid. Die verantwoordelijkheid hoort daarom buiten de class: de class Cart met dependency injection rechtstreeks afhankelijk maken van een API request handler is een onzalig idee.

Iets soortgelijks geldt voor de methode toonProducten(). Als je de class Cart nog steeds beschouwt als een class die verantwoordelijk is voor het bewaren van references naar producten, dan is het tonen van die producten een verantwoordelijkheid die buiten de class hoort.

Hetzelfde geldt in de vraag voor het berekenen van totalen: dat is niet de verantwoordelijkheid van de container, maar van een class die deze container gebruikt.

Maak daarom eens een lijstje van dingen die moeten gebeuren (de verantwoordelijkheden) en probeer elke verantwoordelijkheid vervolgens zo veel mogelijk in een aparte class onder te brengen.
 
Ozzie PHP

Ozzie PHP

09/12/2022 12:49:44
Quote Anchor link
Ideaal is het niet, maar topic starter vroeg hoe hij op een nette manier de API kan aanspreken in plaats van rechtstreeks aan te roepen in de class zelf. Over een wel of niet juiste interpretatie van OOP heb ik het niet gehad. Dat is een totaal andere discussie. toonProducten is in het voorbeeld inderdaad niet zaligmakend, want een (library) class hoort niks te tonen. Dat lijkt me eerder thuis te horen in een controller. Maar zoals ik zei, niet te veel waarde hechten aan de code. Het ging er even om hoe je het ene object op een nette manier kunt meegeven aan het andere.
 
Ward van der Put
Moderator

Ward van der Put

09/12/2022 16:11:28
Quote Anchor link
Ozzie PHP op 09/12/2022 12:49:44:
Ideaal is het niet, maar topic starter vroeg hoe hij op een nette manier de API kan aanspreken in plaats van rechtstreeks aan te roepen in de class zelf.

En jouw oplossing is dan de API meegeven in de constructor?! Dat verplaatst het probleem, maar lost het niet op. Daarmee maak je zelfs de gehele class afhankelijk van de API. Ook wanneer je de API helemaal niet nodig hebt.

Je hebt hiermee de deur naar Dependency Hell op een kier gezet.

Welcome to hell.
 
Jorn Reed

Jorn Reed

09/12/2022 16:35:42
Quote Anchor link
Ik heb hier bijvoorbeeld mijn huidige cart class. Graag wil ik de prijs en product naam niet meer via de huidige manier hier in krijgen
```
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php

class Cart {
    protected $membership;
    protected $addons = [];
    protected $signupCosts = 10.00;

    public function addAddon($addonId, $addonName, $addonPrice){
        if(array_key_exists($addonId, $this->addons)){
            unset($this->addons[$addonId]);
        }
else {
            $this->addons[$addonId] = [
                'name' => $addonName,
                'price' => $addonPrice,
            ];
        }
    }


    public function addMembership($membershipId, $membershipName, $membershipPrice){
        $this->membership = [
            'id' => $membershipId,
            'name' => $membershipName,
            'price' => $membershipPrice,
        ];
    }


    public function getTotalPrice(){
        $totalPrice = 0;

        $addonsTotalPrice = array_reduce($this->addons, function($total, $addon){
            $price = number_format((float) $addon['price'], 2, '.', '');
            $total += $price;
            return $total;
        },
0);

        $totalPrice += $addonsTotalPrice;

        if(array_key_exists('price', $this->membership)){
            $totalPrice +=  number_format((float) $this->membership['price'], 2, '.', '');
        }


        $totalPrice +=  number_format((float) $this->signupCosts, 2, '.', '');

        return $totalPrice;
    }


    public function getCartItems(){
        $cartItems = [];
        $cartItems['membership'] = $this->membership;
        $cartItems['addons'] = array_values($this->addons);
        $cartItems['signupCosts'] = $this->signupCosts;

        $cartItems['total'] = number_format((float) $this->getTotalPrice(), 2, '.', '');
        return $cartItems;
    }
}


?>

```
 
Ozzie PHP

Ozzie PHP

09/12/2022 17:03:27
Quote Anchor link
@Ward

Leuk, al die feedback, maar nog leuker als je zelf een voorbeeldje plaatst van hoe het volgens jou wel zou moeten. Ik gaf enkel een korte reactie op hoe je iets anders kunt doen. Ik ken heel zijn framework niet, evenmin als jij (neem ik aan). Wellicht werkt hij niet eens volgens OOP. Anyhow ... ik zou zeggen, geef een mooi voorbeeld van hoe het volgens jou zou moeten. Dat lijkt me een waardevolle toevoeging aan de discussie.
 

10/12/2022 15:58:36
Quote Anchor link
Het is een inkopper, maar het hele idee van object georiënteerd programmeren is dat je van objecten classes maakt.
Wat ik nu zie is dat Cart niet zozeer een winkelwagen is, maar de rekening met alle specificaties, ofwel alle gegevens worden op 1 hoop gegooid. Het zou mooier zijn wanneer Cart alleen producten bevat. Een product verdient dus een eigen Product class:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php

class Product
{
  /** @var string productnaam */
  protected string $name;
  /** @var int prijs in eurocent */
  protected int $price;

  /**
   * ctor
   * @param string $name Productnaam
   * @param int $price Prijs in eurocent
   * @return void (implicit)
   */

  function __construct(string $name, int $price)
  {

    $this->name = $name;
    $this->price = $price;
  }


  /**
   * getter
   * @return Productnaam
   */

  function name() : string
  {
    return $this->name;
  }


  /**
   * getter
   * @return Productprijs in eurocent
   */

  function price() : int
  {
    return $this->price;
  }
}


class ProductBuilder
{
  static function build_product_by_id(int $id) : Product
  {
    $sql = 'SELECT name, price FROM product WHERE id = $1';
    // $data = $db->query_params(sql, $id);
    $data = [['name' => 'Voorbeeld', 'price' => 250]];
    if (count($data) == 0) { trigger_error('Product not found'); }
    $row = $data[0];
    return new Product($row['name'], $row['price']);
  }
}


class Cart
{
  /** @var array $products Producten */
  protected $products;

  /**
   * ctor
   * @return void (implicit)
   */

  function __construct()
  {

    $this->products = [];
  }

  
  /**
   * Voeg product toe aan winkelwagen
   */

  function add_product(Product $product)
  {

    $this->products[] = $product;
  }

  
  /**
   * Voeg product toe aan winkelwagen via product ID
   */

  function add_product_by_id(int $id)
  {

    $this->products[] = ProductBuilder::build_product_by_id($id);
  }


  /**
   * Bereken totale prijs in eurocent
   */

  function total_price() : int
  {
    /** @var int $total Totaalbedrag in eurocent */
    $total = 0;
    foreach ($this->products as $product) {
      $total += $product->price();
    }

    return $total;
  }
}


$cart = new Cart;
$cart->add_product_by_id(1);
$cart->add_product(new Product('Nog een voorbeeld', 300));
print 'Totaalbedrag: ' . $cart->total_price();
?>


In deze trend is de 'signup costs' ook een product, die zou je standaard zo kunnen meenemen:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$cart
= new Cart;
$cart->add_product(new Product('Signup costs', 1000));
...

?>


Belangrijk is om concepten uit elkaar te trekken. Ik weet niet precies hoe je API er uit ziet, maar normaal gesproken wordt onder een API verstaan een generieke manier om code aan te roepen, ongeacht hoe het is geïmplementeerd. Dan kom je op het gebied van interfaces. Met een kleine omweg kan je via JavaScript ook PHP aanroepen. In dat geval kan je vanuit die 'omweg' bovenstaande classes gebruiken.

Wil je Products nog verder uit elkaar trekken, omdat memberships, addons en signup costs verschillende dingen zijn, maar toch allemaal producten, dan kan je dat verder specificeren via inheritance. Maak de class Product eventueel een abstracte class, en maak voor elk soort product een eigen class, die bouwt op de Product class ('extenden'), met daarin de specifieke eigenschappen (methoden en variabelen). Hierover is op PHPHulp.nl een tutorial verschenen.
Gewijzigd op 10/12/2022 16:18:48 door
 
Ward van der Put
Moderator

Ward van der Put

13/12/2022 11:01:58
 

13/12/2022 22:08:12
Quote Anchor link
Ward van der Put op 13/12/2022 11:01:58:

Ik ben eerder ook in de dependency hell getrapt. Men en iedereen heeft het erover hoe je wel MVC zou moeten implementeren. Maar inmiddels weet ik beter, door schade en schande.

PHP zit in zijn geheel gewoon te losjes in elkaar waardoor het vrijwel ondoenlijk is om er echt goed in te programmeren. Dat ze in PHP 8 nog de booleaanse logica fundamenteel moesten herzien is mijn inziens een grote smet.

Ondertussen krijg je aan alle kanten verkeerd advies over raamwerken. Mijn ogen werden pas geopend na het lezen van dit artikel. MVC blijkt nooit bedoeld voor de situatie van PHP! Er blijkt ook uit dat het compleet onzinnig is om voor een enkele thread een database object te maken, en daarvan de pointer mee te geven aan elke class om zo in de dependency hell te belanden. Sommigen zoeken dan hun toevlucht tot de verguisde singleton, maar dat is vaak wel een praktische oplossing.

Nog praktischer zou zijn om het hele OOP-geneuzel in PHP met een enorme korrel zout te nemen. Zo is het nog beter om gewoon een globale static class te maken voor de DB, beter dan dat wordt het toch niet voor de klassieke situatie van een eigen thread per HTTP-request.

En laten we eerlijk zijn, zolang je in PHP dit kan doen zonder dat je een error krijgt:
Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
<?php
if ('Test') { $t = 'Test'; }
var_dump($t);
?>

is het dweilen met de kraan open. Een string kan zomaar gecast worden naar een bool, zonder dat duidelijk is waarvoor. Vervolgens heeft $t gewoon de waarde NULL, zoals elke niet bestaande variabele dat standaard heeft.

Nog los van de waslijst van dingen en dingetjes die aan PHP zijn op te merken (wat is er wel perfect?) merk ik dat het programmeren in een georganiseerde taal als Rust wel een stuk makkelijker gaat. Ja je moet alles uitspellen, maar daarmee betrap je jezelf eerder op foutjes, en er is meer controle en ondersteuning vanuit de ontwikkeltools. Programma's bevatten minder bugs.

Het gaat ook beter omdat OOP ook niet zaligmakend is, het is niet langer het antwoord op alles. Sterker, inheritance wordt niet eens ondersteund. In plaats daarvan ontwerp je code op basis van gedrag, waardoor je allerlei OOP-problemen voor bent.
Gewijzigd op 13/12/2022 22:09:38 door
 
Jorn Reed

Jorn Reed

21/12/2022 09:20:21
Quote Anchor link
Ad Fundum op 10/12/2022 15:58:36:
Het is een inkopper, maar het hele idee van object georiënteerd programmeren is dat je van objecten classes maakt.
Wat ik nu zie is dat Cart niet zozeer een winkelwagen is, maar de rekening met alle specificaties, ofwel alle gegevens worden op 1 hoop gegooid. Het zou mooier zijn wanneer Cart alleen producten bevat. Een product verdient dus een eigen Product class:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php

class Product
{
  /** @var string productnaam */
  protected string $name;
  /** @var int prijs in eurocent */
  protected int $price;

  /**
   * ctor
   * @param string $name Productnaam
   * @param int $price Prijs in eurocent
   * @return void (implicit)
   */

  function __construct(string $name, int $price)
  {

    $this->name = $name;
    $this->price = $price;
  }


  /**
   * getter
   * @return Productnaam
   */

  function name() : string
  {
    return $this->name;
  }


  /**
   * getter
   * @return Productprijs in eurocent
   */

  function price() : int
  {
    return $this->price;
  }
}


class ProductBuilder
{
  static function build_product_by_id(int $id) : Product
  {
    $sql = 'SELECT name, price FROM product WHERE id = $1';
    // $data = $db->query_params(sql, $id);
    $data = [['name' => 'Voorbeeld', 'price' => 250]];
    if (count($data) == 0) { trigger_error('Product not found'); }
    $row = $data[0];
    return new Product($row['name'], $row['price']);
  }
}


class Cart
{
  /** @var array $products Producten */
  protected $products;

  /**
   * ctor
   * @return void (implicit)
   */

  function __construct()
  {

    $this->products = [];
  }

  
  /**
   * Voeg product toe aan winkelwagen
   */

  function add_product(Product $product)
  {

    $this->products[] = $product;
  }

  
  /**
   * Voeg product toe aan winkelwagen via product ID
   */

  function add_product_by_id(int $id)
  {

    $this->products[] = ProductBuilder::build_product_by_id($id);
  }


  /**
   * Bereken totale prijs in eurocent
   */

  function total_price() : int
  {
    /** @var int $total Totaalbedrag in eurocent */
    $total = 0;
    foreach ($this->products as $product) {
      $total += $product->price();
    }

    return $total;
  }
}


$cart = new Cart;
$cart->add_product_by_id(1);
$cart->add_product(new Product('Nog een voorbeeld', 300));
print 'Totaalbedrag: ' . $cart->total_price();
?>


In deze trend is de 'signup costs' ook een product, die zou je standaard zo kunnen meenemen:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
<?php
$cart
= new Cart;
$cart->add_product(new Product('Signup costs', 1000));
...

?>


Belangrijk is om concepten uit elkaar te trekken. Ik weet niet precies hoe je API er uit ziet, maar normaal gesproken wordt onder een API verstaan een generieke manier om code aan te roepen, ongeacht hoe het is geïmplementeerd. Dan kom je op het gebied van interfaces. Met een kleine omweg kan je via JavaScript ook PHP aanroepen. In dat geval kan je vanuit die 'omweg' bovenstaande classes gebruiken.

Wil je Products nog verder uit elkaar trekken, omdat memberships, addons en signup costs verschillende dingen zijn, maar toch allemaal producten, dan kan je dat verder specificeren via inheritance. Maak de class Product eventueel een abstracte class, en maak voor elk soort product een eigen class, die bouwt op de Product class ('extenden'), met daarin de specifieke eigenschappen (methoden en variabelen). Hierover is op PHPHulp.nl een tutorial verschenen.


Thanks dit is precies wat ik zocht. Dit scheidt erg goed de functionaliteit. Het enige wat in mijn geval anders is, is dat ik geen database gebruik, maar een api, maar dan kan ik die sql query gemakkelijk vervangen door een soortgelijke api call. Is het overigens ook handig om per product property een class te maken, of is dat iets te veel van het goede? Vind het zelf niet nodig, maar hoorde dat van iemand.
 
Ward van der Put
Moderator

Ward van der Put

21/12/2022 11:17:37
Quote Anchor link
Jorn Reed op 21/12/2022 09:20:21:
Is het overigens ook handig om per product property een class te maken, of is dat iets te veel van het goede? Vind het zelf niet nodig, maar hoorde dat van iemand.

Ja, dat noemen we value objects (of ook wel data objects):

https://martinfowler.com/bliki/ValueObject.html

https://en.wikipedia.org/wiki/Value_object

Ik gebruik ze zelf zodra je twee of meer dingen hebt die bij elkaar horen. Martin Fowler noemt als voorbeeld de x en y in coördinaten.
 
Jorn Reed

Jorn Reed

21/12/2022 11:27:11
Quote Anchor link
Maar wat is het voordeel om bijvoorbeeld voor een product, een aparte class te maken voor bijvoorbeeld: product naam, prijs en beschrijving?
 
Ward van der Put
Moderator

Ward van der Put

21/12/2022 11:50:37
Quote Anchor link
Dat merk je vanzelf wanneer je bijvoorbeeld niet alleen bedragen in euro's maar ook bedragen in Britse ponden, Zwitserse franken of Amerikaanse dollars moet verwerken. Je hebt dan niet meer genoeg aan alleen een bedrag, maar moet ook overal de valuta weten. Dat is één samenhangend geheel.

Een persoonsnaam is een ander, misschien wat ingewikkelder voorbeeld. Ik raak al meer dan 50 jaar de "van der" in mijn achternaam kwijt. En dat bij hele grote bedrijven waarvan je wel meer verwacht: dit jaar was het raak/mis bij ING en KPN.

Het probleem is onder andere dat een systeem dat alleen een voornaam plus achternaam verwerkt moet samenwerken met een systeem dat de drie-eenheid voornaam, tussenvoegsels en achternaam gebruikt. En dan heb je ook nog Amerikaanse systemen waarin de middle name (de F. in John F. Kennedy) wordt misbruikt voor Nederlandse tussenvoegsels.

Dit soort problemen kun je voorkomen (en oplossen) door de verantwoordelijkheid bij één class Name te leggen. Andere classes hoeven dan geen onderscheid meer te maken tussen allerlei soort namen en onderdelen van namen: ze gebruiken alleen de class Name.
 
Jorn Reed

Jorn Reed

21/12/2022 11:53:01
Quote Anchor link
Ah oke klinkt logisch, in mijn geval heb ik voor een product het bedrag in euros genoeg aan. Dus dat hoeft niet echt complex te zijn, maar ik snap inderdaad wat je bedoelt.
 
Ozzie PHP

Ozzie PHP

21/12/2022 13:15:50
Quote Anchor link
Ward legt het keurig uit. Het hangt wel af van de complexiteit van je systeem, en van de complexiteit van een functie. Als je gewoon een vaste waarde ophaalt, dan hoef je daar niet per se een aparte class voor te maken.

Bijv. je moet een intern productnummer (sku) ophalen. Dat kan gewoon als volgt:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
<?php

public function getSku(): string {
  return $this->sku;
}


?>

Dat kan ook vaak met een productnaam. Wat Ward als voorbeeld geeft van iemands naam kan in een aparte class, mits er een bepaalde vorm van complexiteit met die functie samenhangt. En dan hangt het ook nog af van de mate van complexiteit. Bijv. stel je wilt dat ieder woord met een hoofdletter begint. Als dat het enige is, dan zou ik daar geen aparte class voor maken.

Ander voorbeeld: stel een user heeft een username, dat is dan gewoon een string, net als hierboven in het voorbeeld van de sku. Daar hoef je geen aparte class voor te maken.

Het is een beetje je eigen keuze. OOP geeft richtlijnen, maar het zijn geen verplichtingen. Je moet vooral voor jezelf kunnen verantwoorden waarom je iets op een bepaalde manier doet, en daar moet je consequent (belangrijk) in zijn.

Een website voor de kruidenier op de hoek, richt zich op de lokale markt en kan daarom om andere vereisten en benodigdheden vragen, dan een commercieel framework dat zich richt op (inter)nationale transacties met verschillende valuta.

Werk je in je eentje of in een team aan de website/webshop? Werk je er alleen aan, dan bepaal jij de spelregels. Werk je met meerdere mensen eraan, dan zul je spelregels moeten opleggen waar iedereen zich aan houdt.

Het gebruiken van een class voor iedere property kan ook zo'n spelregel (standaard) zijn. Niet omdat het per se altijd noodzakelijk is, maar omdat het consistent is en iedereen dan weet dat een property wordt opgehaald via een class.

Kortom, doe wat in jouw situatie nuttig is. Zorg er vooral voor dat je je keuzes kunt verantwoorden en dat je consistent bent in je werkwijze.

Hoop dat je iets aan deze extra toevoeging hebt ;-)
 
Jorn Reed

Jorn Reed

21/12/2022 13:45:52
Quote Anchor link
Yes! Bedankt voor de uitleg, het is nu wel een stuk duidelijker en het hangt dus gewoon echt af van de situatie. Voor nu zijn mijn vragen door jullie heel netjes beantwoord en neem ik de adviezen in de toekomst zeker mee. Want ik ga echt wel een keer in een situatie belanden, waar een property een heel object moet zijn met extra functionaliteit.
 
Ozzie PHP

Ozzie PHP

21/12/2022 14:21:23
Quote Anchor link
Graag gedaan! Als laatste advies zou ik je willen meegeven om gewoon over alles wat je doet de eerste keer goed na te denken. Als je er 1 keer heel goed over nadenkt, dan kun je de voor jou meest geschikte werkwijze kiezen en kun je die alle volgende keren opnieuw gebruiken (zonder dat je er dan weer opnieuw over moet gaan nadenken).

Dat kan over alles gaan. Bijvoorbeeld ter inspiratie: dit topic. Wat is je return waarde?

Maar zo zijn er heel veel dingen waar je over kunt nadenken. En het is goed om dat 1 keer heel goed te doen, zodat je daarop volgende code consistent is.

Een ander voorbeeld.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
<?php

class {

  public function foo(): ?array {
    // code
  }

}


?>

versus

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

class
{

  public function foo() : array|null
  {
    // code
  }

}


?>

Allebei helemaal prima, maar merk op dat de posities van de accolades en de dubbele punt anders is. Net als array|null versus ?array.

Er staat in beide gevallen precies hetzelfde, maar de code is anders 'opgemaakt'. Ook daar zijn standaarden voor, maar het voornaamste is dat je iets kiest, en dat consistent doorvoert. Als je dat doet dan heb je voor jezelf (of voor je team) altijd prettig leesbare code.

Succes!
 
Ward van der Put
Moderator

Ward van der Put

21/12/2022 17:58:47
Quote Anchor link
Voor codes en nummers die aan bepaalde eisen moeten voldoen (syntaxis, opmaak, lengte, enzovoort), zou ik dus wél een value object gebruiken. Denk bijvoorbeeld aan EAN-nummers, BSN-nummers en IBAN-nummers.

Maar omdat het vooral een ontwerpkeuze is, zal ik ook uitleggen waarom ik dat doe.

Stel, je doet inderdaad zoiets:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

public function setSku(string $sku): void
{
    $this->sku = $sku;
}


public function getSku(): string
{
    return $this->sku;
}


?>


Als een business case (of de baas) vereist dat de SKU's worden aangepast, dan moet je overal die strings en stringfuncties aanpassen. Had je nou voor SKU's echter een aparte class StockKeepingUnit gebruikt, dan hoeft dat niet: je past alleen die ene class StockKeepingUnit aan en de rest van de code werkt als vanouds:

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

public function setSku(StockKeepingUnit $sku): void
{
    $this->sku = $sku;
}


public function getSku(): StockKeepingUnit
{
    return $this->sku;
}


?>


Goede code is easy to change!

Een value object zelf is zelden heel complex. Ik maak ze gemakshalve meestal stringable, zodat je ze kunt echoën in de juiste syntaxis of je ze in een SQL-query kunt steken, bij een SKU bijvoorbeeld een "WHERE product_id = " . $sku.

Code (php)
PHP script in nieuw venster Selecteer het PHP script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

declare(strict_types=1);

class StockKeepingUnit implements \Stringable
{
     private readonly string $value;
    
     public function __construct(mixed $value)
     {

         // ... Hier kun je eventueel nog allerlei validaties kwijt. ...
         if (empty($value)) throw new \ValueError();
         $this->value = (string) $value;
     }

    
     public function __toString(): string
     {
         return $this->value;
     }
}


?>
 
Ozzie PHP

Ozzie PHP

21/12/2022 20:27:40
Quote Anchor link
Ward van der Put op 21/12/2022 17:58:47:
Voor codes en nummers die aan bepaalde eisen moeten voldoen (syntaxis, opmaak, lengte, enzovoort), zou ik dus wél een value object gebruiken. Denk bijvoorbeeld aan EAN-nummers, BSN-nummers en IBAN-nummers.

Daar kan ik me inderdaad wat bij voorstellen. Daar zit een zekere mate van complexiteit in, en je kunt het op meerdere plekken nodig hebben. In zo'n geval is dat zeker een goed idee.
 

22/12/2022 21:13:31
Quote Anchor link
Ward van der Put op 21/12/2022 11:17:37:
Jorn Reed op 21/12/2022 09:20:21:
Is het overigens ook handig om per product property een class te maken, of is dat iets te veel van het goede? Vind het zelf niet nodig, maar hoorde dat van iemand.

Ja, dat noemen we value objects (of ook wel data objects):

https://martinfowler.com/bliki/ValueObject.html

https://en.wikipedia.org/wiki/Value_object

Ik gebruik ze zelf zodra je twee of meer dingen hebt die bij elkaar horen. Martin Fowler noemt als voorbeeld de x en y in coördinaten.

Dit is een klassiek voorbeeld van hoe OOP kan worden ingezet. Maar dat is niet de meerwaarde van OOP. Want waarom zou je code schrijven die alleen maar data vast houdt? Dat geeft alleen maar overhead.
In plaats daarvan kan je ook dezelfde functies schrijven waar geen geïnstantieerd object omheen zit dat extra geheugen nodig heeft, dat geeft efficiëntere (snellere) code.

Nou is het wel zo dat PHP vanwege de weak typing praktisch geen enkele ondersteuning biedt aan eigen datatypen zoals dat bij andere programmeertalen wel het geval is. Je kunt in PHP bijvoorbeeld geen enum definieren waarin je meerdere scalars of andere objecten combineert, zoals met structs in C. Vanwege dit gebrek zou je kunnen overgaan om voor elke coördinaat een apart object te instantiëren. Maar erg efficiënt is dat natuurlijk niet.
Met value objects word je als programmeur bij gebrek aan beter eigenlijk de totaal verkeerde kant op gestuurd.

Nou hoef je dat niet van mij aan te nemen, ik heb mijn bias. Maar ik ben niet de enige die dat zo ziet, er is sinds ruim anderhalf jaar een RFC voor structs. Het enige nadeel is dat je nog niet kunt weten hoe lang je er op moet gaan wachten, als het al in PHP geïmplementeerd gaat worden. (status van implementatie is "To Be Determined") ...
Gewijzigd op 22/12/2022 21:17:38 door
 

Pagina: 1 2 volgende »



Overzicht Reageren

 
 

Om de gebruiksvriendelijkheid van onze website en diensten te optimaliseren maken wij gebruik van cookies. Deze cookies gebruiken wij voor functionaliteiten, analytische gegevens en marketing doeleinden. U vindt meer informatie in onze privacy statement.