Home

Awesome

PHP Temiz Kod

İçindekiler

  1. Giriş
  2. Değişkenler
  3. Karşılaştırma
  4. Fonksiyonlar
  5. Nesneler ve Veri Yapıları
  6. Sınıflar
  7. SOLID
  8. Kendinizi Tekrar Etmeyin (DRY)
  9. Çeviriler

Giriş

Yazılım mühendisliği prensipleri,Robert C. Martin'in Temiz Kod kitabından, PHP için uyarlanmıştır. Bu bir stil kılavuzu değildir. PHP’de okunabilir, yeniden kullanılabilir ve düzenlenebilir yazılımlar üretmek için bir kılavuzdur.

Buradaki her prensibe tamamen uyulmamalıdır, ve evrensel olarak daha az kabul edilecek. Bunlar ilke ve başka birşey değildir, ama bunlar Temiz Kod yazarlarının uzun yıllara dayanan kolektif deneyimlerine göre kodlanmıştır.

temiz-kod-javascript den ilham alınmıştır.

Çoğu geliştirici hala PHP 5 kullanıyor olsa da, bu makaledeki örneklerin çoğu sadece PHP 7.1+ ile çalışmaktadır.

Değişkenler

Anlamlı ve telaffuz edilebilir değişken isimleri kullanın

Kötü:

$ymdstr = $moment->format('y-m-d');

İyi:

$currentDate = $moment->format('y-m-d');

⬆ yukarı çık

Aynı türden değişkenler için aynı kelimeleri kullanın

Kötü:

getUserInfo();
getUserData();
getUserRecord();
getUserProfile();

İyi:

getUser();

⬆ yukarı çık

Aranabilir isimler kullanın (bölüm 1)

Yazacağımızdan daha fazla kod okuyacağız. Önemli olan okunabilir ve aranabilir kod yazmamızdır. Değişkenleri anlamlı isimlendirmezsek programımızı anlamaya çalışan okuyucularımıza zarar verebiliriz. İsimleri aranabilir yapın.

Kötü:

// 448 ne için ?
$result = $serializer->serialize($data, 448);

İyi:

$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

Aranabilir isimler kullanın (bölüm 2)

Kötü:

// 4 ne için?
if ($user->access & 4) {
    // ...
}

İyi:

class User
{
    const ACCESS_READ = 1;
    const ACCESS_CREATE = 2;
    const ACCESS_UPDATE = 4;
    const ACCESS_DELETE = 8;
}

if ($user->access & User::ACCESS_UPDATE) {
    // düzenle ...
}

⬆ yukarı çık

Açıklayıcı değişkenler kullanın

Kötü:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

Fena Değil:

Daha iyi ama hala regex'e son derece bağlıyız.

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);

İyi:

Alt şablonlara ad vererek regex bağımlılığını azaltın.

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['city'], $matches['zipCode']);

⬆ yukarı çık

Çok fazla iç içe kullanımdan ve geri döndürmeden kaçının (bölüm 1)

Çok fazla if-else ifadeleri kodun takip edilmesini zorlaştırır. Direkt olmak, dolaylı olmaktan iyidir.

Kötü:

function isShopOpen($day): bool
{
    if ($day) {
        if (is_string($day)) {
            $day = strtolower($day);
            if ($day === 'friday') {
                return true;
            } elseif ($day === 'saturday') {
                return true;
            } elseif ($day === 'sunday') {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}

İyi:

function isShopOpen(string $day): bool
{
    if (empty($day)) {
        return false;
    }

    $openingDays = [
        'friday', 'saturday', 'sunday'
    ];

    return in_array(strtolower($day), $openingDays, true);
}

⬆ yukarı çık

Çok fazla iç içe kullanımdan ve geri döndürmeden kaçının (bölüm 2)

Kötü:

function fibonacci(int $n)
{
    if ($n < 50) {
        if ($n !== 0) {
            if ($n !== 1) {
                return fibonacci($n - 1) + fibonacci($n - 2);
            } else {
                return 1;
            }
        } else {
            return 0;
        }
    } else {
        return 'Not supported';
    }
}

İyi:

function fibonacci(int $n): int
{
    if ($n === 0 || $n === 1) {
        return $n;
    }

    if ($n > 50) {
        throw new \Exception('Not supported');
    }

    return fibonacci($n - 1) + fibonacci($n - 2);
}

⬆ yukarı çık

Zihin Haritasından Kaçının

Okuyucuyu, kodunuzdaki değişkenin ne demek olduğunu tercüme etmek için zorlamayın. Direkt olmak, dolaylı olmaktan iyidir.

Kötü:

$l = ['Austin', 'New York', 'San Francisco'];

for ($i = 0; $i < count($l); $i++) {
    $li = $l[$i];
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    // Bekle, yeniden `$li` ne için?
    dispatch($li);
}

İyi:

$locations = ['Austin', 'New York', 'San Francisco'];

foreach ($locations as $location) {
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    dispatch($location);
}

⬆ yukarı çık

Gereksiz bağlam eklemeyin

Sınıf/Nesne isimleriniz bir şey anlatıyorsa, bunu değişken adınızda tekrarlamayın.

Kötü:

class Car
{
    public $carMake;
    public $carModel;
    public $carColor;

    //...
}

İyi:

class Car
{
    public $make;
    public $model;
    public $color;

    //...
}

⬆ yukarı çık

Kısaltmalar veya şartlılar yerine varsayılan argümanları kullanın

İyi Değil:

Bu iyi değil çünkü $breweryName, NULL olabilir.

function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
    // ...
}

Fena Değil:

Bu düşünce bir önceki versiyondan daha anlaşılır ama değişkenin değerini kontrol etse daha iyi olur.

function createMicrobrewery($name = null): void
{
    $breweryName = $name ?: 'Hipster Brew Co.';
    // ...
}

İyi:

tür ipucu kullanabilirsiniz ve $breweryName öğesinin NULL olmadığından emin olun.

function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
    // ...
}

⬆ yukarı çık

Karşılaştırma

Aynı karşılaştırmayı kullanın.

İyi Değil:

Basit karşılaştırma harf dizinleri bir ifadeyi tam sayıya döndürür.

$a = '42';
$b = 42;

if ($a != $b) {
   // İfade her zaman geçer.
}

$a != $b karşılaştırması FALSE değerini döndürüyor ama aslında TRUE! Harf dizini 42, tam sayı olan 42 den farklıdır.

İyi:

Aynı karşılaştırma türü ve değeri karşılaştırır.

$a = '42';
$b = 42;

if ($a !== $b) {
    // İfade doğrulandı.
}

$a !== $b karşılaştırması TRUE değerini döndürür.

⬆ yukarı çık

Fonksiyonlar

Fonksiyon Parametreleri (2 veya daha az ideal)

Fonksiyon parametrelerinin miktarını sınırlamak inanılmaz derecede önemlidir çünkü fonksiyonunuzu test etmeyi kolaylaştırır. Üç leads ten fazlasının olması, her bir ayrı argümana sahip tonlarca farklı durumu test etmeniz gereken bir kombinatoryal patlamaya yol açar.

Sıfır argümanlar ideal durumdur. Bir veya iki argüman iyidir fakat üç argümandan kaçınılmalıdır. Bundan daha fazlası birleştirilmelidir. Genellikle iki argümandan fazlası varsa fonksiyonunuz çok fazlasını yapmaya çalışır. Olmadığı durumlarda çoğu zaman yüksek seviye bir nesne bir argüman olarak yeterli olacaktır.

Kötü:

function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
    // ...
}

İyi:

class MenuConfig
{
    public $title;
    public $body;
    public $buttonText;
    public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config): void
{
    // ...
}

⬆ yukarı çık

Fonksiyonlar bir şey yapmalı

Bu, yazılım mühendisliğinde açık ara en önemli kuraldır. Fonksiyonlar bir şeyden fazlasını yaptığında bunları oluşturmak, test etmek ve akıl yürütmek daha zordur. Bir fonksiyonu sadece bir eyleme ayırdığında, kolayca düzenlenebilir ve kodunuz daha temiz okunacaktır. Eğer bu kılavuzdan bunda başka bir şey almazsanız bile, birçok geliştiriciden önde olacaksınız.

Kötü:

function emailClients(array $clients): void
{
    foreach ($clients as $client) {
        $clientRecord = $db->find($client);
        if ($clientRecord->isActive()) {
            email($client);
        }
    }
}

İyi:

function emailClients(array $clients): void
{
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}

function activeClients(array $clients): array
{
    return array_filter($clients, 'isClientActive');
}

function isClientActive(int $client): bool
{
    $clientRecord = $db->find($client);

    return $clientRecord->isActive();
}

⬆ yukarı çık

Fonksiyon isimleri ne yaptıklarını söylemeli

Kötü:

class Email
{
    //...

    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// Bu nedir? Mesaj için `handle` ? Şimdi bir dosyaya mı yazıyoruz ?
$message->handle();

İyi:

class Email 
{
    //...

    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// Temiz ve açık
$message->send();

⬆ yukarı çık

Fonksiyonlar sadece bir seviye soyutlama olmalıdır

Birden fazla soyutlama seviyeniz olduğu zaman fonksiyonunuz genellikle çok fazla şey yapıyordur. Fonksiyonları ayırmak tekrar kullanılabilirlik ve daha kolay test edilebilirlik sağlar.

Kötü:

function parseBetterJSAlternative(string $code): void
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            // ...
        }
    }

    $ast = [];
    foreach ($tokens as $token) {
        // lex...
    }

    foreach ($ast as $node) {
        // parse...
    }
}

Çok Kötü:

Bazı fonksiyonellikleri gerçekleştirdik ama parseBetterJSAlternative() fonksiyonu hala çok karmaşık ve test edilebilir değil.

function tokenize(string $code): array
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            $tokens[] = /* ... */;
        }
    }

    return $tokens;
}

function lexer(array $tokens): array
{
    $ast = [];
    foreach ($tokens as $token) {
        $ast[] = /* ... */;
    }

    return $ast;
}

function parseBetterJSAlternative(string $code): void
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // parse...
    }
}

İyi:

En iyi çözüm parseBetterJSAlternative() fonksiyonundaki bağımlılıkları ortadan kaldırmaktır.

class Tokenizer
{
    public function tokenize(string $code): array
    {
        $regexes = [
            // ...
        ];

        $statements = explode(' ', $code);
        $tokens = [];
        foreach ($regexes as $regex) {
            foreach ($statements as $statement) {
                $tokens[] = /* ... */;
            }
        }

        return $tokens;
    }
}

class Lexer
{
    public function lexify(array $tokens): array
    {
        $ast = [];
        foreach ($tokens as $token) {
            $ast[] = /* ... */;
        }

        return $ast;
    }
}

class BetterJSAlternative
{
    private $tokenizer;
    private $lexer;

    public function __construct(Tokenizer $tokenizer, Lexer $lexer)
    {
        $this->tokenizer = $tokenizer;
        $this->lexer = $lexer;
    }

    public function parse(string $code): void
    {
        $tokens = $this->tokenizer->tokenize($code);
        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // parse...
        }
    }
}

⬆ yukarı çık

Bayrakları, fonksiyon parametreleri olarak kullanmayın.

Bayraklar, kullanıcılarınıza bu fonksiyonun birden fazla şey yapacağını söyler. Fonksiyonlar bir şey yapmalıdır. Eğer bir boolean tabanlı farklı kod yollarını takip ediyorsanız, fonksiyonlarınızı bölün.

Kötü:

function createFile(string $name, bool $temp = false): void
{
    if ($temp) {
        touch('./temp/'.$name);
    } else {
        touch($name);
    }
}

İyi:

function createFile(string $name): void
{
    touch($name);
}

function createTempFile(string $name): void
{
    touch('./temp/'.$name);
}

⬆ yukarı çık

Yan Etkilerden Kaçının

Bir fonksiyon, bir değeri almak ve başka değer veya değerleri geri döndürmek dışında bir şey yaparsa bir yan etki üretir. Bir yan etki bir dosyaya yazılabilir, global değişkenleri değiştirebilir veya bir yabancıya bütün paranızı yanlışlıkla bağlayabilir.

Şimdi, bir sebeple programında yan etkilere ihtiyacınız var. Önceki örnekteki gibi, belki bir dosyaya yazmanız gerekebilir. Yapmak istediğiniz şey, yaptığınızı merkezileştirmektir. Belirli bir dosyaya birkaç fonksiyon ve sınıflar yazılmasına gerek yoktur. Bunu yapan bir servis var. Bir ve sadece bir tane.

Ana nokta, herhangi bir yapıya sahip olmayan nesneler arasındaki paylaşım durumu gibi ortak tuzaklardan kaçınmaktır. Herhangi bir şeye göre yazılabilen ve yan etkilerin meydana geldiği yeri merkezileştirmeyen değişebilen veri türleri kullanılmalıdır. Eğer bunu yapabiliyorsanız diğer programcıların büyük çoğunluğundan daha mutlu olacaksınız.

Kötü:

// Global değişkenler, aşağıdaki fonksiyonları referans alır.
// Bu ismi kullanan başka fonksiyonumuz olsaydı, şimdi bir dizi olurdu ve bozulurdu.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName(): void
{
    global $name;

    $name = explode(' ', $name);
}

splitIntoFirstAndLastName();

var_dump($name); // ['Ryan', 'McDermott'];

İyi:

function splitIntoFirstAndLastName(string $name): array
{
    return explode(' ', $name);
}

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);

var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];

⬆ yukarı çık

Global Fonksiyonlar yazmayın

Global kirletme birçok dilde kötü bir uygulama yöntemidir. Çünkü başka bir kütüphane ile çakışabilir ve API'nızın kullanıcısı ürününüzden istisna elde edene kadar bilemezdiniz. Hadi bir örnek düşünelim: Eğer konfigürasyon dizisine sahip olmak isteseniz ne olurdu? config() gibi global fonksiyon yazabilirsiniz ama aynı şeyi yapmaya çalışan başka bir kütüphaneyle çakışabilir.

Kötü:

function config(): array
{
    return  [
        'foo' => 'bar',
    ]
}

İyi:

class Configuration
{
    private $configuration = [];

    public function __construct(array $configuration)
    {
        $this->configuration = $configuration;
    }

    public function get(string $key): ?string
    {
        return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
    }
}

Yapılandırmayı yükleyin ve Configuration sınıfının örneğini oluşturun.

$configuration = new Configuration([
    'foo' => 'bar',
]);

Ve şimdi uygulamanızda Configuration örneğini kullanmalısınız.

⬆ yukarı çık

Singleton desenini kullanmayın

Singeton bir anti-pattern'dir. Brian Button'un yorumuyla:

  1. Genellikle global örnek kullanırlar, neden bu kadar kötü ki? Çünkü bunları arayüzlerde göstermek yerine uygulamanızın kodlarında bağımlılıkları saklıyorsunuz. Global bir şeyler yapmak, kod kokusu etrafında dolaşmamaktır.
  2. Tek Sorumluluk Prensibi (SRP)'ni ihlal ediyorlar : kendi kreasyonlarını ve yaşam döngülerini kontrol ettikleri gerçeğiyle.
  3. Bunlar doğal olarak kodun sıkı sıkıya bağlanmış olmasına neden olurlar. Birçok durumda testi zorlaştırmak için onları kandırır.
  4. Uygulamanın yaşam süresi kadar durum taşıyorlar. Başka bir test yaptıktan sonra birim testleri için büyük bir hiç olan testlerin sıralı olması gereken bir durumla karşılaşabilirsiniz. Neden? Çünkü her birim testi diğerlerinden bağımsız olmalıdır.

Problemin kökü hakkında Misko Hevery 'in çok güzel düşünceleri var.

Kötü:

class DBConnection
{
    private static $instance;

    private function __construct(string $dsn)
    {
        // ...
    }

    public static function getInstance(): DBConnection
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // ...
}

$singleton = DBConnection::getInstance();

İyi:

class DBConnection
{
    public function __construct(string $dsn)
    {
        // ...
    }

     // ...
}

DBConnection sınıfının örneğini oluşturun ve DSN ile yapılandırın.

$connection = new DBConnection($dsn);

Ve şimdi uygulamanızda DBConnection örneğini kullanmalısınız.

⬆ yukarı çık

Koşulları kapsülleyin

Kötü:

if ($article->state === 'published') {
    // ...
}

İyi:

if ($article->isPublished()) {
    // ...
}

⬆ yukarı çık

Olumsuz koşullardan kaçının

Kötü:

function isDOMNodeNotPresent(\DOMNode $node): bool
{
    // ...
}

if (!isDOMNodeNotPresent($node))
{
    // ...
}

İyi:

function isDOMNodePresent(\DOMNode $node): bool
{
    // ...
}

if (isDOMNodePresent($node)) {
    // ...
}

⬆ yukarı çık

Koşullardan kaçının

Bu imkansız bir iş gibi görünüyor. Bunu ilk duyduğunda, birçok insan şöyle der; "Bir if koşulu olmadan nasıl bir şey yapayım ki?" Bunun cevabı ise çoğu durumda aynı işi gerçekleştirmek için polimorfizm(polymorphism) kullanabilmenizdir. Genellikle ikinci soru ise; "tamam bu harika ama neden bunu yapmak isterdim?" Bunun cevabı öğrendiğimiz bir önceki temiz kod kavramıdır: bir fonksiyon sadece bir şey yapmalı. if koşuluna sahip sınıflar ve fonksiyonlarınız olduğu zaman, kullanıcılarınıza fonksiyonunuzun birden fazla şey yaptığınız söylersiniz. Unutmayın, sadece bir şey yapın.

Kötü:

class Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        switch ($this->type) {
            case '777':
                return $this->getMaxAltitude() - $this->getPassengerCount();
            case 'Air Force One':
                return $this->getMaxAltitude();
            case 'Cessna':
                return $this->getMaxAltitude() - $this->getFuelExpenditure();
        }
    }
}

İyi:

interface Airplane
{
    // ...

    public function getCruisingAltitude(): int;
}

class Boeing777 implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude();
    }
}

class Cessna implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}

⬆ yukarı çık

Tür kontrolünden kaçının (bölüm 1)

PHP türlendirilmemiş, bu da fonksiyonlarınızın herhangi bir türde argümanı alabileceğini anlamına geliyor. Bu özgürlük bazen sizi zora sokabilir ve fonksiyonlarınızda tür kontrolü yapmak cazip hale gelir. Bunu yapmaktan kaçınmanın bir çok yolu vardır. Dikkate alınacak ilk şey tutarlı API'lardır.

Kötü:

function travelToTexas($vehicle): void
{
    if ($vehicle instanceof Bicycle) {
        $vehicle->pedalTo(new Location('texas'));
    } elseif ($vehicle instanceof Car) {
        $vehicle->driveTo(new Location('texas'));
    }
}

İyi:

function travelToTexas(Traveler $vehicle): void
{
    $vehicle->travelTo(new Location('texas'));
}

⬆ yukarı çık

Tür kontrolünden kaçının (bölüm 2)

Dizeler, tamsayılar ve diziler gibi basit ilkel değerler ile çalışıyorsanız ve PHP 7+ kullanıyorsanız ve polimorfizmi kullanamıyorsanız ama yine de tür kontrolü yapmanız gerektiğini düşünüyorsanız, tür beyanı veya strict(katı) modunu dikkate almalısınız. Standart PHP sözdiziminin üstünde size statik yazım sağlar. Manuel tür kontrolü ile ilgili problem, aldığınız sahte "tür-güvenliği" nin kaybolan okunabilirliği telafi etmemesi için ekstra gereksiz kelime gerektirmesidir. PHP'nizi temiz tutun, iyi testler yazın ve iyi bir kod incelemesine sahip olun. Aksi taktirde, tüm bunları katı tür beyanı yada katı(strict) mod ile yapın.

Kötü:

function combine($val1, $val2): int
{
    if (!is_numeric($val1) || !is_numeric($val2)) {
        throw new \Exception('Must be of type Number');
    }

    return $val1 + $val2;
}

İyi:

function combine(int $val1, int $val2): int
{
    return $val1 + $val2;
}

⬆ yukarı çık

Ölü kodu kaldırın

Ölü kod, tekrarlanan kod kadar kötüdür. Kod tabanınızda tutmak için bir sebep yok. Eğer çağrılmıyorsa ondan kurtulun! Hala ihtiyacınız varsa versiyon geçmişinizde hala güvende olacak.

Kötü:

function oldRequestModule(string $url): void
{
    // ...
}

function newRequestModule(string $url): void
{
    // ...
}

$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

İyi:

function requestModule(string $url): void
{
    // ...
}

$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

⬆ yukarı çık

Nesneler ve Veri Yapıları

Nesne kapsülleme kullanın

PHP'de, metodlar için public, protected ve private anahtar kelimeler ayarlayabilirsiniz. Bunu kullanarak bir nesne üzerinde özellik değişikliklerini kontrol edebilirsiniz.

Ek olarak, bu Açık/Kapalı prensibinin bir parçasıdır.

Kötü:

class BankAccount
{
    public $balance = 1000;
}

$bankAccount = new BankAccount();

// Ayakkabı al...
$bankAccount->balance -= 100;

İyi:

class BankAccount
{
    private $balance;

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

    public function withdraw(int $amount): void
    {
        if ($amount > $this->balance) {
            throw new \Exception('Amount greater than available balance.');
        }

        $this->balance -= $amount;
    }

    public function deposit(int $amount): void
    {
        $this->balance += $amount;
    }

    public function getBalance(): int
    {
        return $this->balance;
    }
}

$bankAccount = new BankAccount();

// Ayakkabı al...
$bankAccount->withdraw($shoesPrice);

// Bakiyeyi al
$balance = $bankAccount->getBalance();

⬆ yukarı çık

Nesnelerin private/protected üyelere sahip olmasını sağlayın

Bu sebeple, varsayılan olarak private kullanın ve harici sınıflara erişim sağlamanız gerektiğinde public/protected kullanın.

Daha fazla bilgi için, bu konuda Fabien Potencier'ın yazdığı blog yazısını okuyabilirsiniz.

Kötü:

class Employee
{
    public $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->name; // Çalışan Adı: John Doe

İyi:

class Employee
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

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

$employee = new Employee('John Doe');
echo 'Employee name: '.$employee->getName(); // Çalışan Adı: John Doe

⬆ yukarı çık

Sınıflar

Kalıtım üzerinden birleşimi tercih edin

Gang of Four(Dörtlü Çete)'un ünlü Tasarım Desenleri'nde belirtildiği gibi, yapabileceğiniz yerlerde kompozisyonu kalıtıma tercih etmelisiniz. Kalıtımın kullanılması için pek çok iyi sebep ve kompozisyon kullanmak için birçok iyi sebep vardır. Bu ilkenin ana noktası, eğer aklınız içgüdüsel olarak kalıtıma giderse, kompozisyonun problemini daha iyi modellemeyi düşünmeye çalışın. Bazı durumlarda yapabilir.

O zaman merak ediyor olabilirsiniz, "kalıtımımı ne zaman kullanmalıyım?" Bu senin probleminize bağlı ama kalıtımın kompozisyondan daha anlamlı olduğu zamanın iyi bir listesidir:

  1. Kalıtımınız "bir" ilişkiyi temsil eder ve ilişkinin "bir" ilişkisi yoktur (Human->Animal vs. User->UserDetails).
  2. Temel sınıflardan kodu tekrar kullanabilirsiniz (İnsanlar tüm hayvanlar gibi hareket edebilir).
  3. Bir temel sınıfı değiştirerek türetilmiş sınıflarda global değişiklikler yapmak istersiniz (Hareket ettikleri zaman tüm hayvanların kalori giderlerini değiştirin).

Kötü:

class Employee 
{
    private $name;
    private $email;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    // ...
}

// Kötü çünkü Employees vergi verileri "var".
// EmployeeTaxData bir Employee türü değil.

class EmployeeTaxData extends Employee 
{
    private $ssn;
    private $salary;
    
    public function __construct(string $name, string $email, string $ssn, string $salary)
    {
        parent::__construct($name, $email);

        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}

İyi:

class EmployeeTaxData 
{
    private $ssn;
    private $salary;

    public function __construct(string $ssn, string $salary)
    {
        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}

class Employee 
{
    private $name;
    private $email;
    private $taxData;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    public function setTaxData(string $ssn, string $salary)
    {
        $this->taxData = new EmployeeTaxData($ssn, $salary);
    }

    // ...
}

⬆ yukarı çık

Akıcı (Fluent) arayüzlerden kaçının

Akıcı arayüz, Metod Zincirleme kullanarak kaynak kodun okunabilirliği arttırmaya çalışan nesne tabanlı bir API'dir.

Bazı bağlamlar olsa da kodun (Örneğin PHPUnit Mock Builder veya Doctrine Query Builder) ayrıntılarını azalttığı sıklıkla yapıcı madde olsa da, sıklıkla bir bedeli vardır:

  1. Kapsüllemeyi bozar.
  2. Dekoratörleri bozar.
  3. Testte sahte nesne kullanmak daha zordur.
  4. Okuması zor olan farklı işlemler yapar.

Daha fazla bilgi için, bu konuda Marco Pivetta 'nın yazdığı blog yazısını okuyabilirsiniz.

Kötü:

class Car
{
    private $make = 'Honda';
    private $model = 'Accord';
    private $color = 'white';

    public function setMake(string $make): self
    {
        $this->make = $make;

        // NOT: Bunu zincirleme için döndüyor.
        return $this;
    }

    public function setModel(string $model): self
    {
        $this->model = $model;

        // NOT: Bunu zincirleme için döndüyor.
        return $this;
    }

    public function setColor(string $color): self
    {
        $this->color = $color;

        // NOT: Bunu zincirleme için döndüyor.
        return $this;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = (new Car())
  ->setColor('pink')
  ->setMake('Ford')
  ->setModel('F-150')
  ->dump();

İyi:

class Car
{
    private $make = 'Honda';
    private $model = 'Accord';
    private $color = 'white';

    public function setMake(string $make): void
    {
        $this->make = $make;
    }

    public function setModel(string $model): void
    {
        $this->model = $model;
    }

    public function setColor(string $color): void
    {
        $this->color = $color;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();

⬆ yukarı çık

final sınıflarını tercih edin

final mümkün olduğunda kullanılmalıdır:

  1. Kontrolsüz kalıtım zincirini önler.
  2. Kompozisyonu teşvik eder.
  3. Tek Sorumluluk Desenini teşvik der.
  4. Geliştiricilerin, protected metodlara erişmek için sınıfı geliştirmesi yerine public metodlarınızı kullanmasını teşvik eder.
  5. Sınıfını kullanmayan uygulamalarda bozulma olmadan kodunu değiştirmene izin verir.

Tek şart sınıfınızın arayüz kullanmasıdır ve başka hiçbir metod tanımlanmamasıdır.

Daha fazla bilgi için, bu konuda Marco Pivetta (Ocramius) 'nın yazdığı blog yazısını okuyabilirsiniz.

Kötü:

final class Car
{
    private $color;
    
    public function __construct($color)
    {
        $this->color = $color;
    }
    
    /**
     * @return string The color of the vehicle
     */
    public function getColor() 
    {
        return $this->color;
    }
}

İyi:

interface Vehicle
{
    /**
     * @return string The color of the vehicle
     */
    public function getColor();
}

final class Car implements Vehicle
{
    private $color;
    
    public function __construct($color)
    {
        $this->color = $color;
    }
    
    /**
     * {@inheritdoc}
     */
    public function getColor() 
    {
        return $this->color;
    }
}

⬆ yukarı çık

SOLID

SOLID, Robert Martin'in bahsettiği ilk beş prensip için Michael Feathers tarafından ortaya çıkarılmış bir mnemonik kısaltmadır.

Tek Sorumluluk Prensibi (SRP)

Temiz Kodda belirtildiği gibi, "Bir sınıfın değişmesi için asla birden fazla sebep olmamalıdır". Bir sınıfı, uçuşunuzda sadece bir bavul alabildiğiniz gibi tek fonksiyonellikle sıkıştırmak daha caziptir. Bununla ilgili bir sorun, sınıfınızın kavramsal olarak birleşemeyeceği ve değişmesi için birçok sebep vereceğidir. Bir sınıfı değiştirmek için gereken süreyi en aza indirmek önemlidir. Bu önemlidir çünkü bir sınıfta çok fazla fonksiyonellik varsa ve bir parçasını değiştirirsen, kod tabanınızda bulunan diğer bağımlı modülleri nasıl etkileyeceğini kestirmek zordur.

Kötü:

class UserSettings
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }

    private function verifyCredentials(): bool
    {
        // ...
    }
}

İyi:

class UserAuth 
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function verifyCredentials(): bool
    {
        // ...
    }
}

class UserSettings 
{
    private $user;
    private $auth;

    public function __construct(User $user) 
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }

    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}

⬆ yukarı çık

Açık/Kapalı Prensibi (OCP)

Bertrand Meyer'in belirttiği gibi, "yazılım varlıkları (sınıflar, modüller, fonksiyonlar vb.) ilaveye açık olmalı ama değişiklik için kapatılmalıdır." Bu ne anlama geliyor? Bu prensip temel olarak kullanıcıların mevcut kodu değiştirmeden yeni fonksiyonlar eklemelerine izin vermeniz gerektiğini belirtir.

Kötü:

abstract class Adapter
{
    protected $name;

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

class AjaxAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();

        $this->name = 'ajaxAdapter';
    }
}

class NodeAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();

        $this->name = 'nodeAdapter';
    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();

        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }

    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }

    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}

İyi:

interface Adapter
{
    public function request(string $url): Promise;
}

class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}

⬆ yukarı çık

Liskov'un Yerine Geçme Prensibi (LSP)

Bu çok basit bir kavram için korkutucu bir terimdir. "Eğer S, T'nin bir alt türüyse, o zaman T tipi nesneler bu programın istenen özelliklerinden herhangi birini değiştirmeden S tipi (Örneğin, S türündeki nesneler T türündeki nesnelerin yerine geçebilir) nesnelerle değiştirilebilir (uygunluk, yapılan görev, vb.)." Bu daha da korkunç bir açıklamadır.

Bunun için en iyi açıklama, bir ebeveyn ve bir çocuk sınıfınız varsa yanlış sonuçlar almadan, çocuk sınıfı ve temel sınıfı dönüşümlü olarak değiştirilebilir. Bu hala kafa karıştırıcı olabilir. Bu yüzden klasik, Kare-Dikdörtgen örneğine bir bakalım. Matematiksel olarak kare bir dikdörtgendir ama kalıtım aracığıyla ama bunu ilişkiyi kullanarak modelliyorsanız, kısa sürede başınız ağrıyabilir.

Kötü:

class Rectangle
{
    protected $width = 0;
    protected $height = 0;

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

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

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

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

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

function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
 
    // BAD: Will return 25 for Square. Should be 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}

$rectangles = [new Rectangle(), new Square()];

foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

İyi:

En iyi yöntem dörtgenlerini ayırmak ve iki şekil için de daha genel bir alt türü ayrıştırmaktır.

Kare ve dikdörtgen görüntüde benzer olsa da aslında farklılardır. Bir kare, eşkenar dikdörtgene daha benzerdir, bir dikdörtgen de paralelkenara daha benzerdir ama alt tür değillerdir. Bir kare, bir dikdörtgen bir eşkenar dikdörtgen ve bir paralelkenar kendi özellikleri olan farklı şekillerdir. Fakat benzerlerdir.

interface Shape
{
    public function getArea(): int;
}

class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;

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

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

class Square implements Shape
{
    private $length = 0;

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

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

function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}

$shapes = [new Rectangle(4, 5), new Square(5)];

foreach ($shapes as $shape) {
    printArea($shape);
}

⬆ yukarı çık

Arayüz Ayırma Prensibi (ISP)

ISP, "Müşteriler kullanmadıkları arayüzlere bağlı olmaya zorlanmamalıdır." der.

Bu prensibi gösteren iyi bir örneğe bakarsak, büyük ayarlama nesnelerine ihtiyaç duyan sınıflar sınıflar vardır. Müşterinin, çok fazla sayıda seçenek kurmaya ihtiyacı olmaması yararlıdır. Çünkü genelde o ayarlamaların hepsine ihtiyaçları olmayacaktır. Onları opsiyonel yapmak "şişman arayüz" olmasını engeller.

Kötü:

interface Employee
{
    public function work(): void;

    public function eat(): void;
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....çalışıyor
    }

    public function eat(): void
    {
        // ...... öğle arasında yemek yiyor
    }
}

class RobotEmployee implements Employee
{
    public function work(): void
    {
        //.... çok daha fazla çalışıyor
    }

    public function eat(): void
    {
        //.... robot yemek yemez ama bu metodu uygulamak zorundadır
    }
}

İyi:

Her işçi bir çalışan değil ama her çalışan bir işçidir.

interface Workable
{
    public function work(): void;
}

interface Feedable
{
    public function eat(): void;
}

interface Employee extends Feedable, Workable
{
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....çalışıyor
    }

    public function eat(): void
    {
        //.... öğle arasında yemek yiyor
    }
}

// robot sadece çalışabilir
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....çalışıyor
    }
}

⬆ yukarı çık

Bağlılığı Tersine Çevirme Prensibi (DIP)

Bu prensip iki temel öğeyi barındırır:

  1. Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. İkisi de soyutlamaya bağlı olmalıdır.
  2. Soyutlamalar, detaylara bağlı olmamalıdır. Detaylar, soyutlamalara bağlı olmalıdır.

Bunu başta anlamak zordur ama PHP frameworkleri(Symfony gibi) ile çalıştıysanız, Dependency Injection (DI) formunda bu prensibin uygulandığını görmüşsünüzdür. Aynı konseptler olmadıklarında DIP düşük seviyeli modüllerinin detaylarını bilerek yüksek seviyeli modülleri saklar ve ayarlar. Bunu DI üzerinden tamamlayabilir. Bunun en büyük yararlarından biri de modüller arasındaki eşleşmeyi azaltır. Eşleşme çok kötü bir geliştirme desenidir çünkü kodunuzu yeniden düzenlemeyi zorlaştırır.

Kötü:

class Employee
{
    public function work(): void
    {
        // ....çalışıyor
    }
}

class Robot extends Employee
{
    public function work(): void
    {
        //.... çok daha fazla çalışıyor
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

İyi:

interface Employee
{
    public function work(): void;
}

class Human implements Employee
{
    public function work(): void
    {
        // ....çalışıyor
    }
}

class Robot implements Employee
{
    public function work(): void
    {
        //.... çok daha fazla çalışıyor
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

⬆ yukarı çık

Kendinizi Tekrar Etmeyin (DRY)

DRY prensibini gözlemlemeye çalışın.

Tekrarlanan kodlardan kaçınmak için mutlaka en iyisini yapın. Tekrarlanan kod kötüdür çünkü bir mantığı değiştirmek gerekirse bir şeyi değiştirmek için birden fazla yer olduğu anlamına geliyor.

Bir restoran işlettiğinizi düşünün ve malzeme takibini yapıyorsunuz: tüm domates, soğan, sarımsak, baharat, ve benzerlerinin. Bu işte birden çok liste kullanırsanız içinde domates olan bir yemek servis ettiğinizde tüm listeleri güncellemeniz gerekir. Sadece bir listeniz olursa, güncelleyecek sadece bir yer olur.

Genelde tekrarlanan kod vardır çünkü iki veya daha fazla çok az farklı şey vardır. Birçok ortak yönleri vardır ama farklılıkları, oldukça benzer şeyleri yapan iki veya daha fazla ayrı fonksiyonunuzun olmasına sizi zorlar. Tekrarlayan kodları kaldırmak, bir dizi farklı şeyleri sadece bir fonksiyon/modül/sınıf ile halledebilen soyutlamalar oluşturmak demektir.

Soyutlamayı doğru yapmak çok önemlidir. Bu yüzden Sınıflar bölümündeki SOLID prensiplerini izlemelisiniz. Kötü soyutlamalar, tekrarlanan kodlardan daha kötü olabilir, o yüzden dikkat edin! Söylediğim gibi, iyi bir soyutlama yapabiliyorsanız, yapın! Kendinizi tekrar etmeyin, yoksa bir şeyi değiştirmek istediğiniz zaman kendinizi birçok yeri güncellerken bulursunuz.

Kötü:

function showDeveloperList(array $developers): void
{
    foreach ($developers as $developer) {
        $expectedSalary = $developer->calculateExpectedSalary();
        $experience = $developer->getExperience();
        $githubLink = $developer->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

function showManagerList(array $managers): void
{
    foreach ($managers as $manager) {
        $expectedSalary = $manager->calculateExpectedSalary();
        $experience = $manager->getExperience();
        $githubLink = $manager->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

İyi:

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        $expectedSalary = $employee->calculateExpectedSalary();
        $experience = $employee->getExperience();
        $githubLink = $employee->getGithubLink();
        $data = [
            $expectedSalary,
            $experience,
            $githubLink
        ];

        render($data);
    }
}

Çok İyi:

Kodun kompakt bir versiyonunu kullanmak iyidir.

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        render([
            $employee->calculateExpectedSalary(),
            $employee->getExperience(),
            $employee->getGithubLink()
        ]);
    }
}

⬆ yukarı çık

Çeviriler

Başka diller de mevcuttur:

⬆ yukarı çık