Eval är ond
Först och främst:använd inte eval()
om det inte finns en bra anledning. Och det finns aldrig en bra anledning .
i värsta fall eval()
gör din applikation sårbar för injektionsattacker och den är dessutom väldigt långsam. Lite forskning avslöjar massor av anledningar till varför eval är ett stort nej-nej.
Spara inte din beräkningskod i databasen
Om du gör det och du vill byta från PHP till ett annat språk skulle du fortfarande ha PHP-kod i din databas. Det gör det verkligen svårt att migrera språk. Du bör alltid sträva efter att göra så många delar av din ansökan så oberoende som möjligt.
I det här fallet skulle du tight-couple språket du använder till databasen. Det är en dålig praxis.
De enda möjligheterna att köra dina beräkningar från databasen skulle också vara att utvärdera dem (vilket är dåligt, se ovan) eller att ta isär strängen med strängoperationer eller regex som orsakar onödiga ansträngningar.
Det handlar om Strategi
För att lösa ditt problem måste du köra kod beroende på vilken beräkning du behöver. Det kan antingen göras med switch-case-statement eller if-statement. Men det är inte heller en särskilt elegant lösning. Föreställ dig att du skulle behöva utföra andra operationer innan du beräknar i framtiden, eller utöka funktionaliteten. Du skulle behöva uppdatera alla dina ärenden eller if-statement.
Det finns ett snyggt designmönster som kallas Strategy Pattern . Strategimönstret löser problem när ett användningsfall kan hanteras olika, vilket förmodligen är vad du vill.
Du vill beräkna något (use-case) och det finns olika beräkningstyper för det (olika strategier)
Så fungerar det
För att implementera strategimönstret behöver du i princip tre saker.
- En klass där du injicerar dina strategier. Det är i grunden ett omslag för dina strategiuppgifter.
- Ett gränssnitt som kommer att implementeras av dina strategier
- Dina strategier
Ditt gränssnitt kan se ut så här:
<?php
interface CalculatableInterface {
public function calculate();
}
Gränssnittet kommer att se till att alla dina strategier ger en metod för att faktiskt köra beräkningen. Inget speciellt.
Därefter kanske du vill ha en basklass som tar dina beräkningsoperatorer som konstruktorargument och lagrar dem i egenskaper.
<?php
abstract class Calculatable {
protected $valueA;
protected $valueB;
public function __construct($valueA, $valueB)
{
$this->valueA = $valueA;
$this->valueB = $valueB;
}
}
Nu börjar det bli allvar. Vi implementerar våra strategier.
<?php
class Division extends Calculatable implements CalculatableInterface {
public function calculate()
{
return ($this->valueB != 0) ? $this->valueA / $this->valueB : 'NA';
}
}
class Percentage extends Calculatable implements CalculatableInterface {
public function calculate()
{
return ($this->valueB != 0) ? (100 / $this->valueB) * $this->valueA : 'NA';
}
}
Naturligtvis kan du rensa upp den här lite, men det jag vill påpeka här är klassdeklarationen.
Vi utökar vår Calculatable
klass så att vi kan skicka de aritmetiska operationerna via konstruktorn och vi implementerar CalculatableInterface
som säger till vår klass:"Hej! Du måste ange en beräkningsmetod, jag bryr mig inte om du vill eller inte.
Vi får se senare varför detta är en integrerad del av mönstret.
Så vi har två konkreta klasser som innehåller den faktiska koden för själva aritmetiska operationen. Om du någonsin skulle behöva det kan du enkelt ändra det som du ser. För att lägga till fler operationer lägg bara till en annan klass.
Nu ska vi skapa en klass där våra strategier kan injiceras. Senare kommer du att instansiera ett objekt av den här klassen och arbeta med det.
Så här ser det ut:
<?php
class Calculator {
protected $calculatable;
public function __construct( CalculatableInterface $calculatable )
{
$this->calculatable = $calculatable;
}
public function calculate()
{
return $this->calculatable->calculate();
}
}
Den viktigaste delen här är konstruktören. Se hur vi anger vårt gränssnitt här. Genom att göra det ser vi till att endast ett objekt kan injiceras (Dependency Injection ) vars klass implementerar gränssnittet . Vi behöver inte kräva någon konkret klass här. Det är den avgörande punkten här.
Det finns också en beräkningsmetod där. Det är bara ett omslag för vår strategi att utföra sin beräkningsmetod.
Slutar ihop det
Så nu behöver vi bara skapa ett objekt i vår Calculator
klass och skicka ett objekt från en av våra strategiklasser (som innehåller koden för de aritmetiska operationerna).
<?php
//The corresponding string is stored in your DB
$calculatable = 'Division';
$calc = new Calculator( new $calculatable(15, 100) );
echo $calc->calculate();
Försök att ersätta strängen som är lagrad i $calculatable
till Percentage
och du ser att operationen för att beräkna procentsatsen kommer att utföras.
Slutsats
Strategimönstret gjorde att du kunde skapa ett rent gränssnitt för att arbeta med dynamiska uppgifter som bara görs konkreta under körning. Varken din databas behöver veta hur vi beräknar saker, eller din faktiska miniräknare gör det. Det enda vi behöver se till är att koda mot ett gränssnitt som ger en metod för att låta oss beräkna saker.