Kombinera flera objekt i PHP med hjälp av ”dependency injection”

En fråga som dyker upp ganska ofta för nybörjare inom objektorienterad (OO) PHP är hur man kombinerar olika objekt med varandra. Hur får man ett objekt att vara medveten om och kunna använda ett annat? Hur kan jag hämta värden och använda metoder i ett objekt inuti ett annat?

Det finns bra sätt och så finns det mindre bra sätt och dependency injection är ett av de bättre. Det är ett väldigt grundläggande tankesätt i OO/OOP, så det är väl investerad tid att lära känna bättre. Och bäst av allt, det är väldigt lätt att greppa och lära sig använda!

Tänk att du har ett enkelt objekt som sedan behöver kunna utnyttja i ett annat. Låt oss hålla det riktigt simpelt:

// Nödvändig klass
class Klass
{
	// Returnera output från Klass
	function output()
	{
		return "Output från Klass";
	}
}

Detta objektet returnerar helt enkelt bara en textsträng när vi anropar metoden output(). Denna väldigt enkla klassen behöver du nu skapa ett objekt av för att också senare kunna använda det i ett annat objekt. Ett sätt att göra det på (men inget bra sätt) är att använda globala variabler:

// En klass som är beroende av ett Klass-objekt
class BeroendeKlass
{
	// Returnera output från det lagrade Klass-objektet
	function output()
	{
		global $klass;
		return $klass->output();
	}
}
// Skapa ett Klass-objekt som du sedan kan använda i BeroendeKlass
$klass = new Klass();
$beroende = new BeroendeKlass();
echo $beroende->output();

Här skapar vi då ett Klass-objekt och lagrar i $klass. Men detta objektet kommer vi vanligtvis inte åt i andra objekt, i detta fallet BeroendeKlass. Så på rad sju gör vi en riktig fuling och hämtar objektet från global namnrymd för att sedan kunna använda den direkt.

Så varför är då detta farligt? Jo, BeroendeKlass är helt beroende av Klass, den fungerar inte utan det objektet, men objektet är helt beroende av att du skapar upp en global variabel där ett Klass-objekt ligger lagrat. Vad händer om $klass inte är definierat? Eller objektet skulle ligga lagrat med ett annat variabelnamn? Eller rent av råka skrivas över med något helt annat! Nej, det här är inte så säkert alls och ska undvikas till varje pris.

Okej tänker du, då kan vi ju flytta in skapande av Klass-objektet in i BeroendeKlass istället. På det viset är vi helt säkra på att objektet är åtkomligt och vi behöver inte hämta den utifrån. Du kan välja att göra det direkt i konstruktorn enligt:

class BeroendeKlass
{
	private $klass;

	function __construct()
	{
		// Skapa och lagra Klass-objektet
		$this->klass = new Klass();
	}

	// Returnera output från det lagrade Klass-objektet
	function output()
	{
		return $this->klass->output();
	}
}
$beroende = new BeroendeKlass();
echo $beroende->output();

Detta fungerar väl bra? Objektet skapas automatiskt upp (rad åtta) i konstruktorn och du kan använda den utan problem i den egna metoden. Sanningen är att det faktiskt fungerar alldeles utmärkt och säkerheten är betydligt bättre än den var i det första exemplet med en global variabel. Vi kan tex. inte glömma att skapa det nödvändiga objektet, eller lagra det i fel variabel, för det hjälper oss konstruktorn med.

Men vad händer om du behöver byta ut Klass-objektet mot ett NyKlass-objekt som har en annan output-metod (med samma namn)? Då tvingas du in och pilla i BeroendeKlass och byta ut instansieringen av Klass till NyKlass. Fast egentligen vill du bara glömma BeroendeKlass, för du vet att den fungerar och du vill inte göra ändringar i den i onödan. Du vet att det kan leda till buggar. Det kan också vara så att du använder objektet på olika ställen i samma applikation, fast du behöver kunna använda olika output-objekt (Klass och NyKlass). Då är vi garanterat rökta och kan inte göra mycket annat än att ställa onödiga villkor, eller värre, skapa ytterligare en klass.

Enter Dependency Injection (DI)! (jag har ingen bra svensk översättning tyvärr. Beroendeinjektion eller Injektion av beroende låter bara konstigt).

Låt oss titta på följande exempel:

class BeroendeKlass
{
	private $klass;

	// Ta emot ett argument med ditt skapade Klass-objekt
	// direkt vid instansieringen av objektet
	function __construct($class)
	{
		// Skapa och lagra Klass-objektet
		$this->klass = $class;
	}

	// Returnera output från det lagrade Klass-objektet
	function output()
	{
		return $this->klass->output();
	}
}

// Skapa BeroendeKlass-objekt och injicera in ett Klass-objekt till konstruktorn
$beroende = new BeroendeKlass(new Klass());
echo $beroende->output();

Nu använder vi oss av DI genom att skapa båda objekten på samma rad. På samma gång som BeroendeKlass-objektet skapas så skjuter vi in ett Klass-objekt också. Vi injicerar det nödvändiga objektet rakt in i det behövande objektet direkt vid skapandet. Man behöver inte använda konstruktorn för detta, utan vi hade lika gärna kunnat använda en metod (setter) för att lagra objektet efter instansieringen. Men genom att använda konstruktorn så kan vi vara helt säkra på att vi inte kan skapa objektet utan att skicka med objektet som den också är beroende av.

Denna metoden har många fördelar och inte många nackdelar. Nu kan vi skicka in olika objekt som handskas med olika outputs utan att koden i BeroendeKlass påverkas av våra val. Vi behöver inte lita på globala objekt som flyter omkring oskyddade i den globala namnrymden.

En stor fara i vårt exempel är dock att vi inte kan vara säkra på att användaren via DI skickar in rätt typ av objekt. Det kan man lätt åtgärda att kontrollera att objektet är av rätt typ i konstruktorn. Genom att använda abstrakta klasser kan man också använda olika implementeringar (Klass, NyKlass m.m.) och ändå vara helt säker på att objekten har en output-metod. Men det går jag inte in på i detta inlägget.

Lär mer om Dependency Injection på Wikipedia: http://en.wikipedia.org/wiki/Dependency_injection

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *