Link Search Menu Expand Document (external link)

BE 1.2 OOP i PHP

Table of contents


Objektorienterad programmering med object och class i PHP

  • object - representation av verkliga ting. Varje objekt har en uppsättning attribut och handlingar som skiljer dem från andra objekt av samma klass.

  • class - definitionen av hur ett objekt ska se ut, vad det kan göra, en slags mall för objekt.


Definiera klass i PHP

<?php
class Book
{
}
class Customer
{
}
?>

Att göra nya objekt via klasser kallas instansiera objekt.

Gör ett nytt objekt genom att använda klassen ovan:

$book1 = new Book();
$customer1 = new Customer();

Konventioner struktur

  • Varje klass sparas i en egen fil som har samma namn som klassen och slutar med .php
  • Namn på klassen anges i PascalCase
  • En fil ska bara innehålla kod för en klass
  • Inuti klassen ska har vi properties, sedan constructor och slutligen resten av metoderna

Egenskaper/properties

I Book.php (klassen)

class Book
{
    public $title;
    public $author;
    public $available;
    public $isbn;
}

Tilldela värde till klassens properties i objektet ovan

$book1 = new Book();
$book1->title = "1984";
$book1->author = "George Orwell";
$book1->available = 11;
var_dump($book1); // object(Book)#1 (4) { ["title"]=> string(4) "1984" ["author"]=> string(13) "George Orwell" ["available"]=> int(11) ["isbn"]=> NULL }

$isbn är inte satt, så den blir NULL men den fungerar fortfarande. PHP känner av vilken typ det är samt hur många instanser/objekt som gjorts av klassen. Varje instans tilldelas sina egna värden.

Andra objektet av klassen Book (#2)

$book2 = new Book();
$book2->title = "To Kill a Mockingbird";
var_dump($book2); // object(Book)#2 (4) { ["title"]=> string(21) "To Kill a Mockingbird" ["author"]=> NULL ["available"]=> NULL ["isbn"]=> NULL }

Metoder

Funktioner som definieras inuti en klass kallas metoder. Som funktioner får metoder argument från själva objektet/klassen.

Samma som med properties ska det stå publicföre om de ska vara tillgängliga i globala scopet.

this hänvisar till det objekt som anropar metoden. Hade funktionen varit utanför klassen/objektet hade this varit variabeln för objektet (till exempel $book1).

Exempel

public function printableTitle(): string
{
    $result = "<i>{$this->title}</i> - {$this->author}";
    if (!$this->available) {
        $result .= " <b>not available</b>";
    }
    return $result;
}
echo $book1->printableTitle();
echo $book2->printableTitle();

1984 - George Orwell

To Kill a Mockingbird - not available

Constructor

Metod för att instansiera nya objekt av klassen på ett enklare sätt utan att behöva upprepa sig. Körs bara när instansen skapas.

Ser ut som vanliga funktioner men namnges alltid __construct, och de har inte ett return-statement eftersom de alltid måste returnera den nya instansen av klassen.

public function __construct(
    string $title,
    string $author,
    float $isbn,
    int $available
) {
    $this->title = $title;
    $this->author = $author;
    $this->isbn = $isbn;
    $this->available = $available;
}

Kan nu instansieras:

$book1 = new Book("1984", "George Orwell", 9780547249643, 12);

Det går också att skriva default-värden, t.ex:

int $available = 0

I index.php:

<?php
include_once 'Book.php';

$book1 = new Book("1984", "George Orwell", 9780547249643, 12);

echo $book1->title; // 1984
?>

Scope

  • public: Egenskap eller metod som är tillgänglig överallt. Vilken klass eller kod som helst kan nå den.
  • private: Tillåter endast åtkomst för medlemmar av samma klass. Om A och B är instanser av klassen C, så kan A nå properties och metoder av B.
  • protected: Tillåter åtkomst för medlemmar av samma klass och instanser av klasser som ärver från den.

Klass med privata properties och värden i Customer.php. Dessa går bara att komma åt inifrån själva klassen. Publik constructor för att kunna skapa nya instanser med den.

class Customer
{
    private $id;
    private $firstname;
    private $surname;
    private $email;

    public function __construct(
        int $id,
        string $firstname,
        string $surname,
        string $email
    ) {
        $this->id = $id;
        $this->firstname = $firstname;
        $this->surname = $surname;
        $this->email = $email;
    }
}

I index.php:

<?php
include_once 'Customer.php';

$customer1 = new Customer(1, 'John', 'Doe', 'joe.doe@mail.com');

echo $customer1->email; // FATAL ERROR!!!
// pga private.
// går inte att komma åt här utanför klassen.
?>
var_dump($customer1);
// printar ut: object(Customer)#1 (4) { ["id":"Customer":private]=> int(1) ["firstname":"Customer":private]=> string(4) "John" ["surname":"Customer":private]=> string(3) "Doe" ["email":"Customer":private]=> string(16) "joe.doe@mail.com" }

Inkapsling

För att komma åt att läsa eller ändra i objekt där properties är private används inkapsling genom metoder som är public.

Genom att använda så kallade setter-och getter-metoder kan vi läsa eller uppdatera den interna strukturen på klassen, utan att koden som nyttjar klassen behöver ändras. Detta kallas för information hiding.

Exempel getter

Publik metod för att kunna se id:t globalt, utan att kunna ändra det.

public function getId(): int
{
    return $this->id;
}
echo $customer1->getId(); // printar: 1

Exempel setter

Publik metod som ändrar värdet på en specifik property.

public function setEmail(string $email)
{
    $this->email = $email;
}
$customer1->setEmail("abc@mail.com");
var_dump($customer1);
// { ["id":"Customer":private]=> int(1) ["firstname":"Customer":private]=> string(4) "John" ["surname":"Customer":private]=> string(3) "Doe" ["email":"Customer":private]=> string(12) "abc@mail.com" }

Arv

En klass kan ärva saker från en annan klass, via extends.

Exempel klass

class Person
{
    protected $firstname;
    protected $surname;

    public function __construct(string $firstname, string $surname)
    {
        $this->firstname = $firstname;
        $this->surname = $surname;
    }

    public function getFirstname(): string
    {
        return $this->firstname;
    }

    public function getSurname(): string
    {
        return $this->surname;
    }

}

Klass som ärver/extendar klassen ovan

class Customer extends Person
{
    private $id;
    private $email;

    public function __construct(
        int $id,
        string $firstname,
        string $surname,
        string $email
    ) {
        parent::__construct($firstname, $surname);

        $this->id = $id;
        $this->email = $email;
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function setEmail(string $email)
    {
        $this->email = $email;
    }

}

I constructor-metoden tar vi då in properties från föräldern med parent::__construct($firstname, $surname);

Instanserna som ärver kan overrida metoderna i parent-klassen, genom att definiera dem igen.

Exempel

<?php

class Rick
{
    public function sayHi()
    {
        echo "Hi, I am Rick. *burps*";
    }
}

class Morty extends Rick
{
    public function sayHi()
    {
        echo "Hi, I am a Morty. Oh geez.";
    }
}

$rick = new Rick();
$morty = new Morty();
echo $rick->sayHi(); // Hi, I am Rick *burps*.
echo $morty->sayHi(); // Hi, I am Morty. Oh geez.

Abstract class

En abstrakt klass är en klass som inte kan instansieras, utan enda syftet är att se till att barnen som ärver den funkar korrekt. Inuti den abstrakta klassen så kan vi styra vilka metoder klassen som ärver MÅSTE implementera.

Nyckelordet absctract framför metodnamnet, då är barnem till klassen tvungna att ha de metoderna.

Exempel

abstract class Customer extends Person
{
    // propterties
    // constructor

    // alla barn som ärver MÅSTE ha de här metoderna:
    abstract public function getMonthlyFee();
    abstract public function getAmountToBorrow();
    abstract public function getType();

    // andra metoder
}

Det går också att bestämma typ på return värdet i metoderna:

abstract public function getMonthlyFee(): float;

Det anges inte i den abstrakta klassen hur de abstrakta metoderna ska fungera, utan bara vad de heter, vad de är för typ av metod och eventuellt typ av return value.


Namespaces

I PHP kan inte två klasser ha samma namn, och för att lösa det så finns det namespaces.

Definiera namespace är bland det första som skrivs i en klass. Varje del av ett namespace separeras med back-slash (\), som om det vore en egen mapp.

Om inget namespace definieras så kommer det läggas i ett bas-namespace (root).

Exempel

namespace Bookstore\Domain;

Vi måste sen använda namespacet i index.php för att det ska fungera.

Exempel i index.php när två klasser har samma namn men olika namespace

<?php

use Bookstore\Domain\Book;
use SuperDuper\Bookstore\Domain\Book as SuperBook;

Fil- och mappstrukur

Mappstrukturen i projektet ska efterlikna namespacet. Rooten för vårat namespace (i detta fallet BookStore) representeras i projektet av en mapp som döps till src. I mappen src så gör vi en mapp som heter Domain, där alla våra klasser ligger.


Statiska properties och metoder (static)

Varje instans av en klass’ properties och metoder är länkade till just den instansen, två instanser kan alltså ha olika värden för samma property.

Ibland finns det kanske properties eller metoder som vi vill ska kunna delas mellan olika instanser, alltå att de länkas till klassen istället för det resulterande objektet. Vi använder då nyckelordet static.

Syntax

private static $lastId = 0;

Exempel med klassen Customer

class Customer
{
    private $id;
    private $email;
    private static $lastId = 0

    public function __construct(
        int $id,
        string $firstname,
        string $surname,
        string $email
    ) {
        if ($id === null) {
            $this->id = ++self::$lastId;
        } else {
            $this->id = $id;
            if ($id > self::$lastId) {
                self::$lastId = $id;
            }
        }

        $this->firstname = $firstname;
        $this->surname = $surname;
        $this->email = $email;
    }
}

Förklaring av koden:

  • Vi sätter variabeln/propertyn $lastId till static, och default-värde 0.
  • Vi tar in $id i constructorn, men istället för att sätta det direkt, så gör vi en if-sats ($id är alltså inte med och sätts med $firstname etc…)
  • I if-satsen:
    • Om $id är lika med null (dvs om det inte är satt):
      • Id sätts enligt följande: $this->id = ++self::$lastId;
      • $lastId = den delade propertyn/variabeln som sätts i klassen och delas mellan alla instanser
      • ++ innebär att vi ökar på den med 1
      • self:: för att komma åt den delade propertyn, alltså det som lagrats i klassen
      • Eftersom klassen minns hur många instanser som har gjorts, så säger vi då att instansen vi ska göra nu ska ha det senaste satta id:t +1
    • Om vi har skickat in ett värde på $id när vi gjorde instansen, dvs att det inte är null (else):
      • Id sätts det till värdet vi skickade in: $this->id = $id;
    • Efter else en till if-sats:
      • Om id:t vi nyss har satt ($id) är större än det senaste satta id:t ($lastId), så sätts $lastId till samma som id:t vi nyss satte ($id)

OBS! När vi hänvisar till en statisk egenskap använder vi self::, inte $this ($this är ju lika med objektet, men nu vill vi komma åt den delade egenskapen i klassen/mallen)

Att använda statiska properties och metoder

Statiska properties och metoder kan användas utan ett instansierat objekt. Eftersom de inte är knutna till objektet utan till själva mallen (klassen) så kan de anropas utan att något objekt instasierats.

Reglerna för synlighet gäller dock fortfarande! Eftersom $lasId är private, behöver vi en publik metod för att hämta ut den.

Exempel

public static function getLastId(): int
{
  return self::$lastId;
}

Exempel på anrop utan existerande instans

Customer::getLastId();

Exempel på anrop med ett instansierat objekt

$customer2::getLastId();

Notera: eftersom det är static så används :: och inte ->


Interface

Ett interface i PHP ser lite ut som en klass, men innehåller bara funktionsdeklarationer. Interfaces definerer namn, argument och funktionstyp, men inte koden i funktionerna (likt abstrakta funktioner). Fördelen med att använda ett interface är att en klass kan använda mer än ett interface åt gången.

Som exempel gör vi om Customer från en abstrakt klass till ett interface. Vi flyttar då all implementation från Customer till Person, eftersom ett interface i PHP INTE får innehålla implementationer eller egenskaper.

Customer som ett interface

<?php

namespace Bookstore\Domain;

interface Customer
{
    public function getMonthlyFee(): float;
    public function getAmountToBorrow(): int;
    public function getType(): string;
}

Implementering av interface

Görs med nyckelordet implements, liknande extends vid arv.

I Basic.php:

<?php

namespace Bookstore\Domain\Customer;

use Bookstore\Domain\Person;
use Bookstore\Domain\Customer;

class Basic extends Person implements Customer
// ...

Basic måste fortfarande extenda Person, så vi får med alla metoder och egenskaper som vi flyttade dit från Customer.

Implementation av flera interfaces per klass (“arv”)

Då vi kan ha fler än ett interface per klass så testar vi att göra ett till som vi kallar för Payer.

Payer interface

<?php

namespace Bookstore\Domain;

interface Payer
{
    public function pay(float $amount);
    public function isExtentOfTaxes(): bool;
}

Basic class

namespace Bookstore\Domain\Customer;

use Bookstore\Domain\Person;
use Bookstore\Domain\Customer;
use Bookstore\Domain\Payer;

class Basic extends Person implements Customer, Payer

OBS!
Då funktionerna i ett interface fungerar som abstrakta metoder i abstrakta klasser, så måste Basic här innehålla metoderna pay() och isExtentOfTaxes().


Patterns/mönster

Ett pattern i kod är en namngiven gruppering av lösningar på vanliga kodproblem. Tanken är inte att applicera patterns överallt, det går emot hela idén. Snarare ska de ses som en riktlinje, och det är viktigt att förstå vad de är för någonting och hur de fungerar.

Typer av patterns

  • Creational:
    Patterns som styr skapandet av objekt, t.ex:
    • Factory
    • Singleton
    • Prototype
  • Structural:
    Patterns som underlättar design genom att identifiera förhållanden mellan entiteter, t.ex:
    • Decorator
    • Façade
  • Behavioral: Patterns som identifierar vanliga kommunikationsmönster mellan objekt, för att sen realisera de mönstren, t.ex:
    • Observer

Factory

Ett pattern som låter oss skapa ett objekt. Då Customer är ett interface så kan vi inte instansiera den. Med factory pattern kan vi säga vilken typ av klass som ska instansieras och sedan göra det dynamiskt.

I src/Domain/Customer/CustomerFactory:

<?php

namespace Bookstore\Domain\Customer;

use Bookstore\Domain\Customer;

class CustomerFactory
{
    public static function factory(
        string $type,
        string $firstname,
        string $surname,
        string $email,
        int $id = null
    ): Customer {
          $classname = __NAMESPACE__ . '\\' . ucfirst($type);
          if (!class_exists($classname)) {
              throw new \InvalidArgumentException('Wrong type.');
          }
          // om klassen finns, gör en ny instans
          return new $classname($firstname, $surname, $email, $id);
      }
}

Förklaring av koden:

  • $type som vi skickar in är vilken typ av kund (vilken klass), här antingen "basic" eller "premium"
  • Variabeln $classname ska vara hela klassnamnet med namespace, för att få det:
    • Konstanten __NAMESPACE__ som innehåller namespacet vi just nu är i
    • En backslash (som vi måste escapa)
    • Typen vi nyss skickade in, med den inbyggda funktionen ucfirst() för att se till att det är stor bokstav i början
    • Nu är alltså $classname === Bookstore\Domain\Customer\Basic om det var "basic" vi skickade in i $type
  • Kolla sedan att klassen faktiskt finns, om inte, kasta en exception
    • Inbyggd funktion class_exists() för att se om en klass finns
    • Inbyggd exception som escapas pga global
  • Om klassen finns, gör en ny instans av den. Argumenten för att göra klassen skickas in.

Nu kan vi då skriva såhär för att göra en ny instans i index.php:

$basic1 = CustomerFactory::factory("basic", "Bosse", "Stenwall", "bosse@datainternet.it");
$premium1 = CustomerFactory::factory("premium", "Vilgot", "Stenwall", "vilgot@datainternet.it");

En factory kan bli så avancerad som det behövs. Finns flera olika varianter att göra det på, men principen är alltid densamma - en fabrik som dynamiskt instansierar nya objekt.