Οι υπηρεσίες δεν χρειάζονται ονόματα

3 χρόνια πριν Από το Jiří Pudil  

Λατρεύω τη λύση της έγχυσης εξαρτήσεων του Nette Framework. Πραγματικά. Αυτή η δημοσίευση είναι εδώ για να μοιραστώ αυτό το πάθος, εξηγώντας γιατί πιστεύω ότι είναι η καλύτερη λύση DI στο σημερινό οικοσύστημα PHP.

(Αυτή η δημοσίευση έχει αρχικά δημοσιευτεί στο blog του συγγραφέα).

Η ανάπτυξη λογισμικού είναι μια ατελείωτη επαναληπτική διαδικασία αφαίρεσης. Βρίσκουμε τις κατάλληλες αφαιρέσεις του πραγματικού κόσμου στη μοντελοποίηση τομέων. Στον αντικειμενοστρεφή προγραμματισμό, χρησιμοποιούμε αφαιρέσεις για να περιγράψουμε και να επιβάλουμε συμβάσεις μεταξύ των διαφόρων παραγόντων εντός του συστήματος. Εισάγουμε νέες κλάσεις στο σύστημα για να ενθυλακώσουμε τις ευθύνες και να καθορίσουμε τα όριά τους και στη συνέχεια χρησιμοποιούμε τη σύνθεση για να χτίσουμε ολόκληρο το σύστημα.

Μιλάω για την παρόρμηση να εξάγουμε τη λογική του ελέγχου ταυτότητας από τον ακόλουθο ελεγκτή:

final class SignInController extends Best\Framework\Controller
{
    public function action(string $username, string $password): Best\Framework\Response
    {
        if ($username !== 'admin' || $password !== 'p4ssw0rd!') {
            return $this->render(__DIR__ . '/error.latte');
        }

        $this->signIn(new Identity($username));
        return $this->redirect(HomepageController::class);
    }
}

Μπορείτε πιθανώς να καταλάβετε ότι ο έλεγχος των διαπιστευτηρίων δεν ανήκει εκεί. Δεν είναι ευθύνη του ελεγκτή να πει ποια διαπιστευτήρια είναι έγκυρα – ακολουθώντας την αρχή της ενιαίας ευθύνης, ο ελεγκτής θα πρέπει να έχει μόνο έναν λόγο για να αλλάξει, και αυτός ο λόγος θα πρέπει να βρίσκεται μέσα στο περιβάλλον χρήστη της εφαρμογής, όχι στη διαδικασία του ελέγχου ταυτότητας.

Ας πάρουμε τον προφανή τρόπο για να ξεφύγουμε από αυτό και να εξάγουμε τη συνθήκη σε μια κλάση Authenticator:

final class Authenticator
{
    public function authenticate(string $username, string $password): bool
    {
        return $username === 'admin' && $password === 'p4ssw0rd!';
    }
}

Τώρα το μόνο που χρειάζεται να κάνουμε είναι να αναθέσουμε από τον ελεγκτή σε αυτόν τον επαληθευτή. Έχουμε κάνει τον αυθεντικοποιητή μια εξάρτηση του ελεγκτή, και ξαφνικά ο ελεγκτής πρέπει να τον πάρει από κάπου:

final class SignInController extends Best\Framework\Controller
{
    public function action(string $username, string $password): Best\Framework\Response
    {
        $authenticator = new Authenticator(); // <== Εύκολο, θα δημιουργήσω ένα νέο!
        if ( ! $authenticator->authenticate($username, $password)) {
            return $this->render(__DIR__ . '/error.latte');
        }

        $this->signIn(new Identity($username));
        return $this->redirect(HomepageController::class);
    }
}

Αυτός ο αφελής τρόπος θα λειτουργήσει. Αλλά μόνο μέχρι να υλοποιηθεί ένας πιο ισχυρός έλεγχος ταυτότητας, ο οποίος θα απαιτεί από τον ελεγκτή ταυτότητας να ρωτάει έναν πίνακα της βάσης δεδομένων με τους χρήστες. Το Authenticator έχει ξαφνικά μια δική του εξάρτηση, ας πούμε ένα UserRepository, το οποίο με τη σειρά του εξαρτάται από μια περίπτωση Connection, η οποία εξαρτάται από τις παραμέτρους του συγκεκριμένου περιβάλλοντος. Αυτό κλιμακώθηκε γρήγορα!

Η δημιουργία περιπτώσεων με το χέρι παντού δεν είναι ένας βιώσιμος τρόπος διαχείρισης των εξαρτήσεων. Γι' αυτό έχουμε το μοτίβο dependency injection, το οποίο επιτρέπει στον ελεγκτή να δηλώνει απλώς την εξάρτησή του από ένα Authenticator, και να αφήνει να είναι πρόβλημα κάποιου άλλου να παρέχει στην πραγματικότητα ένα instance. Και αυτός ο κάποιος άλλος ονομάζεται περιέκτης έγχυσης εξάρτησης (dependeny injection container).

Ο περιέκτης έγχυσης εξαρτήσεων είναι ο ανώτατος αρχιτέκτονας της εφαρμογής – γνωρίζει πώς να επιλύει τις εξαρτήσεις οποιασδήποτε υπηρεσίας μέσα στο σύστημα και είναι υπεύθυνος για τη δημιουργία τους. Τα DI containers είναι τόσο διαδεδομένα στις μέρες μας που σχεδόν κάθε σημαντικό web framework έχει τη δική του υλοποίηση container, ενώ υπάρχουν ακόμα και αυτόνομα πακέτα αφιερωμένα στην έγχυση εξαρτήσεων, όπως το PHP-DI.

Καίγοντας τις πιπεριές

Η πληθώρα των επιλογών παρακίνησε τελικά μια ομάδα προγραμματιστών να αναζητήσουν μια αφαίρεση για να τις κάνουν διαλειτουργικές. Η κοινή διασύνδεση βελτιώθηκε με την πάροδο του χρόνου και τελικά προτάθηκε στην PHP-FIG με την ακόλουθη μορφή:

interface ContainerInterface
{
    public function get(string $id): mixed;
    public function has(string $id): bool;
}

Αυτή η διεπαφή απεικονίζει ένα πολύ σημαντικό χαρακτηριστικό των DI containers: είναι σαν τη φωτιά. Είναι ένας καλός υπηρέτης αλλά μπορεί εύκολα να γίνει ένας κακός κύριος. Είναι τρομερά χρήσιμα εφόσον γνωρίζετε πώς να τα χρησιμοποιείτε, αλλά αν τα χρησιμοποιείτε λανθασμένα, σας καίνε. Σκεφτείτε το ακόλουθο δοχείο:

final class Container implements ContainerInterface
{
    private array $factories = [];
    public function __construct(array $parameters)
    {
        $this->factories['authenticator'] = fn() => new Authenticator($this->get('userRepository'));
        $this->factories['userRepository'] = fn() => new UserRepository($this->get('connection'));
        $this->factories['connection'] = fn() => new Connection($parameters['database']);
    }

    public function get(string $id): mixed { /* . . . */ }
    public function has(string $id): bool { /* . . . */ }
}

Μέχρι στιγμής όλα καλά. Η υλοποίηση φαίνεται καλή σύμφωνα με τα πρότυπα που έχουμε θέσει: ξέρει πράγματι πώς να δημιουργήσει κάθε υπηρεσία στην εφαρμογή, επιλύοντας αναδρομικά τις εξαρτήσεις της. Η διαχείριση των πάντων γίνεται σε ένα μόνο μέρος, και το container δέχεται ακόμη και παραμέτρους, ώστε η σύνδεση με τη βάση δεδομένων να είναι εύκολα παραμετροποιήσιμη. Ωραία!

Αλλά τώρα, βλέποντας τις δύο μόνο μεθόδους του ContainerInterface, μπορεί να μπείτε στον πειρασμό να χρησιμοποιήσετε τον περιέκτη ως εξής:

final class SignInController extends Best\Framework\Controller
{
    public function __construct(
        private ContainerInterface $container,
    ) {}

    public function action(string $username, string $password): Best\Framework\Response
    {
        $authenticator = $this->container->get('authenticator');
        //...
    }
}

Συγχαρητήρια, μόλις κάψατε τις πιπεριές σας. Με άλλα λόγια, το δοχείο έχει γίνει ο κακός κύριος. Γιατί συμβαίνει αυτό;

Πρώτον, βασίζεστε σε ένα αυθαίρετο αναγνωριστικό υπηρεσίας: 'authenticator'. Το Dependency Injection έχει να κάνει με τη διαφάνεια σχετικά με τις εξαρτήσεις κάποιου, και η χρήση ενός τεχνητού αναγνωριστικού αντιβαίνει ευθέως σε αυτή την έννοια: κάνει τον κώδικα να εξαρτάται σιωπηλά από τον ορισμό του εμπορευματοκιβωτίου. Αν τύχει ποτέ να μετονομάσετε την υπηρεσία στο δοχείο, θα πρέπει να βρείτε αυτή την αναφορά και να την ενημερώσετε.

Και το χειρότερο, αυτή η εξάρτηση είναι κρυφή: με μια πρώτη ματιά από έξω, ο ελεγκτής εξαρτάται μόνο από μια αφαίρεση ενός εμπορευματοκιβωτίου. Αλλά ως προγραμματιστής, εσύ πρέπει να γνωρίζεις πώς ονομάζονται οι υπηρεσίες στο δοχείο και ότι μια υπηρεσία που ονομάζεται authenticator είναι στην πραγματικότητα μια περίπτωση της Authenticator. Ο νέος σου συνάδελφος πρέπει να τα μάθει όλα αυτά. Χωρίς λόγο.

Ευτυχώς, μπορούμε να καταφύγουμε σε ένα πολύ πιο φυσικό αναγνωριστικό: τον τύπο της υπηρεσίας. Εξάλλου, αυτό είναι το μόνο που σας ενδιαφέρει ως προγραμματιστής. Δεν χρειάζεται να ξέρετε ποια τυχαία συμβολοσειρά συνδέεται με την υπηρεσία στο δοχείο. Πιστεύω ότι αυτός ο κώδικας είναι πολύ πιο εύκολος τόσο στη συγγραφή όσο και στην ανάγνωση:

final class SignInController extends Best\Framework\Controller
{
    public function __construct(
        private ContainerInterface $container,
    ) {}

    public function action(string $username, string $password): Best\Framework\Response
    {
        $authenticator = $this->container->get(Authenticator::class);
        //...
    }
}

Δυστυχώς, δεν έχουμε δαμάσει ακόμα τις φλόγες. Ούτε στο ελάχιστο. Το μεγαλύτερο πρόβλημα είναι ότι υποβιβάζετε το εμπορευματοκιβώτιο στο ρόλο του εντοπιστή υπηρεσιών, το οποίο είναι ένα τεράστιο αντι-πρότυπο. Είναι σαν να φέρνεις σε κάποιον ολόκληρο το ψυγείο για να μπορέσει να πάρει ένα μόνο σνακ από αυτό – είναι πολύ πιο λογικό να του φέρεις μόνο το σνακ.

Και πάλι, η έγχυση εξαρτήσεων έχει να κάνει με τη διαφάνεια, και αυτός ο ελεγκτής εξακολουθεί να μην είναι διαφανής για τις εξαρτήσεις του. Η εξάρτηση από έναν αυθεντικοποιητή είναι εντελώς κρυμμένη από τον έξω κόσμο, πίσω από την εξάρτηση από το δοχείο. Αυτό κάνει τον κώδικα πιο δύσκολο να διαβαστεί. Ή χρησιμοποιήστε. Ή τη δοκιμή! Η προσομοίωση του authenticator σε ένα unit test απαιτεί τώρα να δημιουργήσετε ένα ολόκληρο container γύρω του.

Και παρεμπιπτόντως, ο ελεγκτής εξακολουθεί να εξαρτάται από τον ορισμό του container, και μάλιστα με αρκετά κακό τρόπο. Αν η υπηρεσία authenticator δεν υπάρχει στο container, ο κώδικας αποτυγχάνει μόνο στη μέθοδο action(), η οποία είναι μια αρκετά καθυστερημένη ανατροφοδότηση.

Μαγειρεύοντας κάτι νόστιμο

Για να είμαστε δίκαιοι, κανείς δεν μπορεί πραγματικά να σας κατηγορήσει για το γεγονός ότι βρεθήκατε σε αυτό το αδιέξοδο. Εξάλλου, απλώς ακολουθήσατε τη διεπαφή που σχεδίασαν και απέδειξαν έξυπνοι προγραμματιστές. Το θέμα είναι ότι όλα τα δοχεία έγχυσης εξαρτήσεων είναι εξ ορισμού και εντοπιστές υπηρεσιών, και αποδεικνύεται ότι το μοτίβο είναι πραγματικά η μόνη κοινή διεπαφή μεταξύ τους. Αλλά αυτό δεν σημαίνει ότι θα πρέπει να τα χρησιμοποιείτε ως εντοπιστές υπηρεσιών. Στην πραγματικότητα, το ίδιο το PSR προειδοποιεί γι' αυτό.

Έτσι μπορείτε να χρησιμοποιήσετε ένα DI container ως καλό υπηρέτη:

final class SignInController extends Best\Framework\Controller
{
    public function __construct(
        private Authenticator $authenticator,
    ) {}

    public function action(string $username, string $password): Best\Framework\Response
    {
        $areCredentialsValid = $this->authenticator->authenticate($username, $password);
        //...
    }
}

Ο ελεγκτής δηλώνει την εξάρτηση ρητά, ξεκάθαρα, διαφανώς στον κατασκευαστή. Οι εξαρτήσεις δεν είναι πλέον κρυμμένες διάσπαρτες στην κλάση. Επιβάλλονται επίσης: ο περιέκτης δεν είναι σε θέση να δημιουργήσει μια περίπτωση της SignInController χωρίς να παρέχει την απαραίτητη Authenticator. Εάν δεν υπάρχει αυθεντικοποιητής στο δοχείο, η εκτέλεση αποτυγχάνει νωρίς, όχι στη μέθοδο action(). Ο έλεγχος αυτής της κλάσης έχει γίνει επίσης πολύ πιο εύκολος, επειδή χρειάζεται μόνο να παριστάνετε την υπηρεσία αυθεντικοποιητή χωρίς κανένα boilerplate του container.

Και υπάρχει μια τελευταία μικροσκοπική, αλλά πολύ σημαντική λεπτομέρεια: έχουμε εισχωρήσει κρυφά τις πληροφορίες σχετικά με τον τύπο της υπηρεσίας. Το γεγονός ότι πρόκειται για μια περίπτωση του Authenticator – που προηγουμένως υπονοούνταν και ήταν άγνωστο στο IDE, στα εργαλεία στατικής ανάλυσης ή ακόμη και σε έναν προγραμματιστή που δεν γνωρίζει τον ορισμό του δοχείου – είναι τώρα στατικά χαραγμένο στην υποδήλωση τύπου της προωθούμενης παραμέτρου.

Το μόνο βήμα που απομένει είναι να διδάξουμε στον περιέκτη πώς να δημιουργήσει και τον ελεγκτή:

final class Container implements ContainerInterface
{
    private array $factories = [];
    public function __construct(array $parameters)
    {
        $this->factories[SignInController::class] = fn() => new SignInController($this->get(Authenticator::class));
        $this->factories[Authenticator::class] = fn() => new Authenticator($this->get(UserRepository::class));
        $this->factories[UserRepository::class] = fn() => new UserRepository($this->get(Connection::class));
        $this->factories[Connection::class] = fn() => new Connection($parameters['database']);
    }

    public function get(string $id): mixed { /* . . . */ }
    public function has(string $id): bool { /* . . . */ }
}

Μπορεί να παρατηρήσετε ότι το δοχείο εξακολουθεί να χρησιμοποιεί εσωτερικά την προσέγγιση εντοπισμού υπηρεσιών. Αλλά αυτό δεν πειράζει, αρκεί να περιέχεται (λογοπαίγνιο με πρόθεση). Το μόνο μέρος έξω από το δοχείο όπου η κλήση της μεθόδου get είναι αποδεκτή, είναι στο index.php, στο σημείο εισόδου της εφαρμογής όπου πρέπει να δημιουργήσετε το ίδιο το δοχείο και στη συνέχεια να ανακτήσετε και να εκτελέσετε την εφαρμογή:

$container = bootstrap();

$application = $container->get(Best\Framework\Application::class);
$application->run();

Το κρυμμένο διαμάντι

Αλλά ας μη σταματήσουμε εδώ, επιτρέψτε μου να πάω τη δήλωση παραπέρα: το μόνο μέρος όπου η κλήση της μεθόδου get είναι αποδεκτή, είναι στο σημείο εισόδου.

Ο κώδικας του δοχείου είναι απλώς καλωδίωση, είναι οδηγίες συναρμολόγησης. Δεν είναι εκτελεστικός κώδικας. Δεν είναι σημαντικός, κατά κάποιο τρόπο. Αν και ναι, είναι κρίσιμος για την εφαρμογή, αυτό συμβαίνει μόνο από την οπτική γωνία του προγραμματιστή. Δεν προσφέρει πραγματικά καμία άμεση αξία στον χρήστη, και θα πρέπει να αντιμετωπίζεται με αυτό κατά νου.

Ρίξτε ξανά μια ματιά στο δοχείο:

final class Container implements ContainerInterface
{
    private array $factories = [];
    public function __construct(array $parameters)
    {
        $this->factories[SignInController::class] = fn() => new SignInController($this->get(Authenticator::class));
        $this->factories[Authenticator::class] = fn() => new Authenticator($this->get(UserRepository::class));
        $this->factories[UserRepository::class] = fn() => new UserRepository($this->get(Connection::class));
        $this->factories[Connection::class] = fn() => new Connection($parameters['database']);
    }

    public function get(string $id): mixed { /* . . . */ }
    public function has(string $id): bool { /* . . . */ }
}

Αυτό καλύπτει μόνο ένα πολύ μικρό και απλό τμήμα της εφαρμογής. Καθώς η εφαρμογή μεγαλώνει, η χειροκίνητη συγγραφή του περιέκτη γίνεται απίστευτα κουραστική. Όπως έχω ξαναπεί, το εμπορευματοκιβώτιο είναι απλώς ένα εγχειρίδιο συναρμολόγησης – αλλά είναι ένα υπερβολικά περίπλοκο εγχειρίδιο, με πολλές σελίδες, αμέτρητες διασταυρούμενες παραπομπές και πολλές προειδοποιήσεις με μικρά γράμματα. Θέλουμε να το μετατρέψουμε σε ένα εγχειρίδιο τύπου ΙΚΕΑ, γραφικό, συνοπτικό και με εικόνες ανθρώπων που χαμογελούν όταν τοποθετούν το ÅUTHENTICATÖR πάνω στο χαλί κατά τη συναρμολόγηση για να μην σπάσει.

Σε αυτό το σημείο μπαίνει στο παιχνίδι το Nette Framework.

Η λύση DI του Nette Framework χρησιμοποιεί το Neon, μια μορφή αρχείου ρυθμίσεων παρόμοια με την YAML, αλλά σε στεροειδή. Έτσι θα ορίζατε το ίδιο δοχείο, χρησιμοποιώντας τη διαμόρφωση Neon:

services:
    - SignInController
    - Authenticator
    - UserRepository
    - Connection(%database%)

Επιτρέψτε μου να επισημάνω δύο αξιοσημείωτα πράγματα: πρώτον, η λίστα των υπηρεσιών είναι πραγματικά μια λίστα, όχι ένας χάρτης κατακερματισμού – δεν υπάρχουν κλειδιά, ούτε τεχνητά αναγνωριστικά υπηρεσιών. Δεν υπάρχει το authenticator, ούτε το Authenticator::class. Δεύτερον, δεν χρειάζεται να απαριθμήσετε ρητά καμία εξάρτηση πουθενά, εκτός από τις παραμέτρους σύνδεσης με τη βάση δεδομένων.

Αυτό συμβαίνει επειδή το Nette Framework βασίζεται στην αυτόματη σύνδεση. Θυμάστε πώς, χάρη στην έγχυση εξαρτήσεων, μπορέσαμε να εκφράσουμε τον τύπο της εξάρτησης σε ένα εγγενές typehint; Το δοχείο DI χρησιμοποιεί αυτές τις πληροφορίες, έτσι ώστε όταν χρειάζεστε μια περίπτωση του Authenticator, να παρακάμπτει εντελώς οποιαδήποτε ονόματα και να βρίσκει τη σωστή υπηρεσία αποκλειστικά και μόνο με βάση τον τύπο της.

Θα μπορούσατε να ισχυριστείτε ότι η αυτόματη σύνδεση δεν είναι ένα μοναδικό χαρακτηριστικό. Και θα είχατε δίκιο. Αυτό που κάνει το δοχείο του Nette Framework μοναδικό είναι η αξιοποίηση του συστήματος τύπων της PHP, ενώ σε πολλά άλλα πλαίσια, το autowiring εξακολουθεί να βασίζεται σε ονόματα υπηρεσιών εσωτερικά. Υπάρχουν σενάρια στα οποία άλλα κοντέινερ υστερούν. Έτσι θα ορίζατε την υπηρεσία authenticator στο DI container του Symfony χρησιμοποιώντας YAML:

services:
  Authenticator: ~

Το τμήμα services είναι ένας χάρτης κατακερματισμού και το κομμάτι Authenticator είναι ένα αναγνωριστικό υπηρεσίας. Το tilde συμβολίζει το null στη YAML, το οποίο το Symfony ερμηνεύει ως “χρησιμοποιήστε το αναγνωριστικό της υπηρεσίας ως τύπο”.

Σύντομα, όμως, οι επιχειρηματικές απαιτήσεις αλλάζουν και πρέπει να υποστηρίξετε τον έλεγχο ταυτότητας μέσω LDAP εκτός από την αναζήτηση σε τοπική βάση δεδομένων. Ως πρώτο βήμα, μετατρέπετε την κλάση Authenticator σε διεπαφή και εξάγετε την αρχική υλοποίηση σε μια κλάση LocalAuthenticator:

services:
  LocalAuthenticator: ~

Ξαφνικά, το Symfony δεν έχει ιδέα. Αυτό συμβαίνει επειδή το Symfony λειτουργεί με ονόματα υπηρεσιών αντί για τύπους. Ο ελεγκτής εξακολουθεί να στηρίζεται σωστά στην αφαίρεση και παραθέτει τη διεπαφή Authenticator ως εξάρτησή του, αλλά δεν υπάρχει καμία υπηρεσία με το όνομα Authenticator στο δοχείο. Πρέπει να δώσετε στο Symfony μια υπόδειξη, για παράδειγμα χρησιμοποιώντας ένα ψευδώνυμο ονομα υπηρεσίας:

services:
  LocalAuthenticator: ~
  Authenticator: '@LocalAuthenticator'

Το Nette Framework, από την άλλη πλευρά, δεν χρειάζεται ονόματα υπηρεσιών ή υποδείξεις. Δεν σας αναγκάζει να αναπαράγετε στη διαμόρφωση τις πληροφορίες που έχουν ήδη εκφραστεί στον κώδικα (μέσω της ρήτρας implements ). Βρίσκεται ακριβώς πάνω στο σύστημα τύπων της PHP. Γνωρίζει ότι το LocalAuthenticator είναι του τύπου Authenticator, και εφόσον είναι η μόνη υπηρεσία που υλοποιεί τη διεπαφή, ευχαρίστως την συνδέει αυτόματα όπου ζητείται η διεπαφή, δεδομένης μόνο αυτής της γραμμής διαμόρφωσης:

services:
    - LocalAuthenticator

Ομολογώ ότι αν δεν είστε εξοικειωμένοι με την αυτόματη σύνδεση, μπορεί να σας φανεί λίγο μαγικό και ίσως χρειαστεί λίγος χρόνος για να μάθετε να το εμπιστεύεστε. Ευτυχώς, λειτουργεί διαφανώς και ντετερμινιστικά: όταν ο περιέκτης δεν μπορεί να επιλύσει μονοσήμαντα τις εξαρτήσεις, πετάει μια εξαίρεση σε χρόνο μεταγλώττισης που σας βοηθά να διορθώσετε την κατάσταση. Με αυτόν τον τρόπο, μπορείτε να έχετε δύο διαφορετικές υλοποιήσεις και να εξακολουθείτε να έχετε καλό έλεγχο του πού χρησιμοποιείται η καθεμία από αυτές.

Συνολικά, η αυτόματη σύνδεση θέτει λιγότερο γνωστικό φορτίο σε εσάς ως προγραμματιστή. Εξάλλου, σας ενδιαφέρουν μόνο οι τύποι και οι αφαιρέσεις, οπότε γιατί ένα DI container να σας αναγκάσει να ενδιαφέρεστε και για τις υλοποιήσεις και τα αναγνωριστικά υπηρεσιών; Και το πιο σημαντικό, γιατί θα πρέπει να σας ενδιαφέρει καν κάποιος περιέκτης εξαρχής; Στο πνεύμα του dependency injection, θέλετε να μπορείτε απλώς να δηλώνετε τις εξαρτήσεις και να είναι πρόβλημα κάποιου άλλου να τις παρέχει. Θέλετε να επικεντρωθείτε πλήρως στον κώδικα της εφαρμογής και να ξεχάσετε τις καλωδιώσεις. Και το DI του Nette Framework σας επιτρέπει να το κάνετε αυτό.

Στα μάτια μου, αυτό καθιστά τη λύση DI της Nette Framework την καλύτερη λύση που υπάρχει στον κόσμο της PHP. Σας δίνει ένα δοχείο που είναι αξιόπιστο και επιβάλλει καλά αρχιτεκτονικά πρότυπα, αλλά ταυτόχρονα είναι τόσο εύκολο στη διαμόρφωση και τη συντήρηση που δεν χρειάζεται να το σκέφτεστε καθόλου.

Ελπίζω αυτή η δημοσίευση να κατάφερε να σας κεντρίσει την περιέργεια. Βεβαιωθείτε ότι έχετε ελέγξει το repository στο Github και τα docs – ελπίζω ότι θα μάθετε ότι σας έχω δείξει μόνο την κορυφή του παγόβουνου και ότι ολόκληρο το πακέτο είναι πολύ πιο ισχυρό.

Τελευταίες θέσεις