Nette Documentation Preview

syntax
Δομή καταλόγου της εφαρμογής
****************************

<div class=perex>

Πώς να σχεδιάσετε μια σαφή και κλιμακούμενη δομή καταλόγου για έργα στο Nette Framework; Θα σας δείξουμε δοκιμασμένες πρακτικές που θα σας βοηθήσουν να οργανώσετε τον κώδικά σας. Θα μάθετε:

- πώς να **δομήσετε λογικά** την εφαρμογή σε καταλόγους
- πώς να σχεδιάζετε τη δομή ώστε να **κλιμακώνεται καλά** καθώς το έργο μεγαλώνει
- ποιες είναι οι **πιθανές εναλλακτικές λύσεις** και τα πλεονεκτήματα ή μειονεκτήματά τους

</div>


Είναι σημαντικό να αναφέρουμε ότι το ίδιο το Nette Framework δεν επιμένει σε κάποια συγκεκριμένη δομή. Είναι σχεδιασμένο ώστε να προσαρμόζεται εύκολα σε οποιεσδήποτε ανάγκες και προτιμήσεις.


Βασική δομή έργου .[#toc-basic-project-structure]
=================================================

Αν και το Nette Framework δεν υπαγορεύει κάποια σταθερή δομή καταλόγων, υπάρχει μια αποδεδειγμένη προεπιλεγμένη διάταξη με τη μορφή [Web Project |https://github.com/nette/web-project]:

/--pre
<b>web-project/</b>
├── <b>app/</b>              ← κατάλογος εφαρμογών
├── <b>assets/</b>           ← SCSS, αρχεία JS, εικόνες..., εναλλακτικά resources/
├── <b>bin/</b>              ← σενάρια γραμμής εντολών
├── <b>config/</b>           ← διαμόρφωση
├── <b>log/</b>              ← καταγεγραμμένα σφάλματα
├── <b>temp/</b>             ← προσωρινά αρχεία, κρυφή μνήμη
├── <b>tests/</b>            ← δοκιμές
├── <b>vendor/</b>           ← βιβλιοθήκες που εγκαθίστανται από τον Composer
└── <b>www/</b>              ← δημόσιος κατάλογος (document-root)
\--

Μπορείτε να τροποποιήσετε ελεύθερα αυτή τη δομή σύμφωνα με τις ανάγκες σας - να μετονομάσετε ή να μετακινήσετε φακέλους. Στη συνέχεια, πρέπει απλώς να προσαρμόσετε τις σχετικές διαδρομές προς τους καταλόγους στο `Bootstrap.php` και ενδεχομένως στο `composer.json`. Δεν χρειάζεται τίποτα άλλο, ούτε πολύπλοκη αναδιαμόρφωση, ούτε συνεχείς αλλαγές. Το Nette διαθέτει έξυπνη αυτόματη ανίχνευση και αναγνωρίζει αυτόματα τη θέση της εφαρμογής, συμπεριλαμβανομένης της βάσης URL της.


Αρχές οργάνωσης κώδικα .[#toc-code-organization-principles]
===========================================================

Όταν εξερευνάτε για πρώτη φορά ένα νέο έργο, θα πρέπει να είστε σε θέση να προσανατολιστείτε γρήγορα. Φανταστείτε να κάνετε κλικ στον κατάλογο `app/Model/` και να δείτε αυτή τη δομή:

/--pre
<b>app/Model/</b>
├── <b>Services/</b>
├── <b>Repositories/</b>
└── <b>Entities/</b>
\--

Από αυτήν, θα μάθετε μόνο ότι το έργο χρησιμοποιεί κάποιες υπηρεσίες, αποθετήρια και οντότητες. Δεν θα μάθετε τίποτα για τον πραγματικό σκοπό της εφαρμογής.

Ας δούμε μια διαφορετική προσέγγιση - **οργάνωση με βάση τους τομείς**:

/--pre
<b>app/Model/</b>
├── <b>Cart/</b>
├── <b>Payment/</b>
├── <b>Order/</b>
└── <b>Product/</b>
\--

Αυτό είναι διαφορετικό - με την πρώτη ματιά είναι σαφές ότι πρόκειται για ιστότοπο ηλεκτρονικού εμπορίου. Τα ίδια τα ονόματα των καταλόγων αποκαλύπτουν τι μπορεί να κάνει η εφαρμογή - λειτουργεί με πληρωμές, παραγγελίες και προϊόντα.

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


Χώροι ονομάτων .[#toc-namespaces]
---------------------------------

Είναι συμβατικό η δομή καταλόγου να αντιστοιχεί σε χώρους ονομάτων στην εφαρμογή. Αυτό σημαίνει ότι η φυσική θέση των αρχείων αντιστοιχεί στο χώρο ονομάτων τους. Για παράδειγμα, μια κλάση που βρίσκεται στο `app/Model/Product/ProductRepository.php` θα πρέπει να έχει χώρο ονομάτων `App\Model\Product`. Αυτή η αρχή βοηθά στον προσανατολισμό του κώδικα και απλοποιεί την αυτόματη φόρτωση.


Ενικός και πληθυντικός αριθμός στα ονόματα .[#toc-singular-vs-plural-in-names]
------------------------------------------------------------------------------

Παρατηρήστε ότι χρησιμοποιούμε ενικό για τους κύριους καταλόγους εφαρμογών: `app`, `config`, `log`, `temp`, `www`. Το ίδιο ισχύει και στο εσωτερικό της εφαρμογής: `Model`, `Core`, `Presentation`. Αυτό συμβαίνει επειδή το καθένα αντιπροσωπεύει μια ενιαία έννοια.

Ομοίως, το `app/Model/Product` αντιπροσωπεύει τα πάντα σχετικά με τα προϊόντα. Δεν το ονομάζουμε `Products` επειδή δεν είναι ένας φάκελος γεμάτος προϊόντα (που θα περιείχε αρχεία όπως `iphone.php`, `samsung.php`). Είναι ένας χώρος ονομάτων που περιέχει κλάσεις για την εργασία με προϊόντα - `ProductRepository.php`, `ProductService.php`.

Ο φάκελος `app/Tasks` είναι στον πληθυντικό επειδή περιέχει ένα σύνολο αυτόνομων εκτελέσιμων σεναρίων - `CleanupTask.php`, `ImportTask.php`. Κάθε ένα από αυτά αποτελεί μια ανεξάρτητη μονάδα.

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


Δημόσιος κατάλογος `www/` .[#toc-public-directory-www]
======================================================

Αυτός ο κατάλογος είναι ο μόνος προσβάσιμος από το διαδίκτυο (το λεγόμενο document-root). Συχνά μπορεί να συναντήσετε το όνομα `public/` αντί για `www/` - είναι απλώς θέμα σύμβασης και δεν επηρεάζει τη λειτουργικότητα. Ο κατάλογος περιέχει:
- [Σημείο εισόδου |bootstrap#index.php] της εφαρμογής `index.php`
- αρχείο `.htaccess` με κανόνες mod_rewrite (για τον Apache)
- Στατικά αρχεία (CSS, JavaScript, εικόνες)
- Ανεβασμένα αρχεία

Για τη σωστή ασφάλεια της εφαρμογής, είναι ζωτικής σημασίας να έχετε [ρυθμίσει |nette:troubleshooting#how-to-change-or-remove-www-directory-from-url] σωστά [το document-root |nette:troubleshooting#how-to-change-or-remove-www-directory-from-url].

.[note]
Ποτέ μην τοποθετείτε το φάκελο `node_modules/` σε αυτόν τον κατάλογο - περιέχει χιλιάδες αρχεία που μπορεί να είναι εκτελέσιμα και δεν πρέπει να είναι προσβάσιμα από το κοινό.


Κατάλογος εφαρμογών `app/` .[#toc-application-directory-app]
============================================================

Αυτός είναι ο κύριος κατάλογος με τον κώδικα της εφαρμογής. Βασική δομή:

/--pre
<b>app/</b>
├── <b>Core/</b>               ← θέματα υποδομής
├── <b>Model/</b>              ← επιχειρησιακή λογική
├── <b>Presentation/</b>       ← παρουσιαστές και πρότυπα
├── <b>Tasks/</b>              ← σενάρια εντολών
└── <b>Bootstrap.php</b>       ← κλάση bootstrap της εφαρμογής
\--

`Bootstrap.php` είναι η [κλάση εκκίνησης της εφαρμογής |bootstrap] που αρχικοποιεί το περιβάλλον, φορτώνει τη διαμόρφωση και δημιουργεί το δοχείο DI.

Ας δούμε τώρα αναλυτικά τους επιμέρους υποκαταλόγους.


Παρουσιαστές και πρότυπα .[#toc-presenters-and-templates]
=========================================================

Έχουμε το τμήμα παρουσίασης της εφαρμογής στον κατάλογο `app/Presentation`. Μια εναλλακτική λύση είναι το σύντομο `app/UI`. Εκεί βρίσκονται όλοι οι παρουσιαστές, τα πρότυπα τους και τυχόν βοηθητικές κλάσεις.

Οργανώνουμε αυτό το επίπεδο ανά τομείς. Σε ένα σύνθετο έργο που συνδυάζει ηλεκτρονικό εμπόριο, blog και API, η δομή θα έμοιαζε ως εξής:

/--pre
<b>app/Presentation/</b>
├── <b>Shop/</b>              ← frontend ηλεκτρονικού εμπορίου
│   ├── <b>Product/</b>
│   ├── <b>Cart/</b>
│   └── <b>Order/</b>
├── <b>Blog/</b>              ← blog
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← διαχείριση
│   ├── <b>Dashboard/</b>
│   └── <b>Products/</b>
└── <b>Api/</b>               ← Τελικά σημεία API
	└── <b>V1/</b>
\--

Αντίθετα, για ένα απλό ιστολόγιο θα χρησιμοποιούσαμε αυτή τη δομή:

/--pre
<b>app/Presentation/</b>
├── <b>Front/</b>             ← εμπρόσθιο άκρο ιστοσελίδας
│   ├── <b>Home/</b>
│   └── <b>Post/</b>
├── <b>Admin/</b>             ← διαχείριση
│   ├── <b>Dashboard/</b>
│   └── <b>Posts/</b>
├── <b>Error/</b>
└── <b>Export/</b>            ← RSS, sitemaps κ.λπ.
\--

Οι φάκελοι όπως `Home/` ή `Dashboard/` περιέχουν παρουσιαστές και πρότυπα. Οι φάκελοι όπως `Front/`, `Admin/` ή `Api/` ονομάζονται **modules**. Τεχνικά, πρόκειται για κανονικούς καταλόγους που χρησιμεύουν για τη λογική οργάνωση της εφαρμογής.

Κάθε φάκελος με έναν παρουσιαστή περιέχει έναν παρουσιαστή με παρόμοιο όνομα και τα πρότυπά του. Για παράδειγμα, ο φάκελος `Dashboard/` περιέχει:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← παρουσιαστής
└── <b>default.latte</b>              ← πρότυπο
\--

Αυτή η δομή καταλόγων αντικατοπτρίζεται στους χώρους ονομάτων των κλάσεων. Για παράδειγμα, το `DashboardPresenter` βρίσκεται στο χώρο ονομάτων `App\Presentation\Admin\Dashboard` (βλ. [αντιστοίχιση παρουσιαστή |#presenter mapping]):

```php
namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	//...
}
```

Αναφερόμαστε στον παρουσιαστή `Dashboard` μέσα στην ενότητα `Admin` στην εφαρμογή χρησιμοποιώντας τον συμβολισμό της άνω και κάτω τελείας ως `Admin:Dashboard`. Στη δράση του `default` στη συνέχεια ως `Admin:Dashboard:default`. Για ένθετες ενότητες χρησιμοποιούμε περισσότερες άνω και κάτω τελεία, για παράδειγμα `Shop:Order:Detail:default`.


Ανάπτυξη ευέλικτης δομής .[#toc-flexible-structure-development]
---------------------------------------------------------------

Ένα από τα μεγάλα πλεονεκτήματα αυτής της δομής είναι το πόσο κομψά προσαρμόζεται στις αυξανόμενες ανάγκες του έργου. Ως παράδειγμα, ας πάρουμε το τμήμα που παράγει XML feeds. Αρχικά, έχουμε μια απλή φόρμα:

/--pre
<b>Export/</b>
├── <b>ExportPresenter.php</b>   ← ένας παρουσιαστής για όλες τις εξαγωγές
├── <b>sitemap.latte</b>         ← πρότυπο για χάρτη σελίδων
└── <b>feed.latte</b>            ← πρότυπο για RSS feed
\--

Με την πάροδο του χρόνου, προστίθενται περισσότεροι τύποι τροφοδοσίας και χρειαζόμαστε περισσότερη λογική για αυτούς... Κανένα πρόβλημα! Ο φάκελος `Export/` γίνεται απλά μια ενότητα:

/--pre
<b>Export/</b>
├── <b>Sitemap/</b>
│   ├── <b>SitemapPresenter.php</b>
│   └── <b>sitemap.latte</b>
└── <b>Feed/</b>
	├── <b>FeedPresenter.php</b>
	├── <b>amazon.latte</b>         ← feed για την Amazon
	└── <b>ebay.latte</b>           ← τροφοδοσία για το eBay
\--

Απλά δημιουργήστε νέους υποφακέλους, χωρίστε τον κώδικα σε αυτούς και ενημερώστε τους συνδέσμους (π.χ. από `Export:feed` σε `Export:Feed:amazon`). Χάρη σε αυτό, μπορούμε να επεκτείνουμε σταδιακά τη δομή όπως χρειάζεται, το επίπεδο φωλιάσματος δεν περιορίζεται με κανέναν τρόπο.

Για παράδειγμα, αν στη διαχείριση έχετε πολλούς παρουσιαστές που σχετίζονται με τη διαχείριση παραγγελιών, όπως `OrderDetail`, `OrderEdit`, `OrderDispatch` κ.λπ. μπορείτε να δημιουργήσετε μια ενότητα (φάκελο) `Order` για καλύτερη οργάνωση, η οποία θα περιέχει (φακέλους για) τους παρουσιαστές `Detail`, `Edit`, `Dispatch` και άλλους.


Τοποθεσία προτύπου .[#toc-template-location]
--------------------------------------------

Στα προηγούμενα παραδείγματα, είδαμε ότι τα πρότυπα βρίσκονται απευθείας στο φάκελο με τον παρουσιαστή:

/--pre
<b>Dashboard/</b>
├── <b>DashboardPresenter.php</b>     ← παρουσιαστής
├── <b>DashboardTemplate.php</b>      ← προαιρετική κλάση προτύπου
└── <b>default.latte</b>              ← πρότυπο
\--

Αυτή η τοποθεσία αποδεικνύεται η πιο βολική στην πράξη - έχετε όλα τα σχετικά αρχεία στη διάθεσή σας.

Εναλλακτικά, μπορείτε να τοποθετήσετε τα πρότυπα σε έναν υποφάκελο `templates/`. Η Nette υποστηρίζει και τις δύο παραλλαγές. Μπορείτε ακόμη και να τοποθετήσετε τα πρότυπα εντελώς εκτός του φακέλου `Presentation/`. Τα πάντα σχετικά με τις επιλογές τοποθέτησης προτύπων μπορείτε να βρείτε στο κεφάλαιο [Αναζήτηση προτύπων |templates#Template Lookup].


Βοηθητικές κλάσεις και συστατικά .[#toc-helper-classes-and-components]
----------------------------------------------------------------------

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

1. **Απευθείας με τον παρουσιαστή** σε περίπτωση που πρόκειται για συγκεκριμένα στοιχεία για τον συγκεκριμένο παρουσιαστή:

/--pre
<b>Product/</b>
├── <b>ProductPresenter.php</b>
├── <b>ProductGrid.php</b>        ← συστατικό για την καταχώριση προϊόντων
└── <b>FilterForm.php</b>         ← φόρμα για φιλτράρισμα
\--

2. **Για την ενότητα** - συνιστούμε τη χρήση του φακέλου `Accessory`, ο οποίος τοποθετείται τακτοποιημένα στην αρχή του αλφαβήτου:

/--pre
<b>Front/</b>
├── <b>Accessory/</b>
│   ├── <b>NavbarControl.php</b>    ← συστατικά για frontend
│   └── <b>TemplateFilters.php</b>
├── <b>Product/</b>
└── <b>Cart/</b>
\--

3. **Για ολόκληρη την εφαρμογή** - στον φάκελο `Presentation/Accessory/`:
/--pre
<b>app/Presentation/</b>
├── <b>Accessory/</b>
│   ├── <b>LatteExtension.php</b>
│   └── <b>TemplateFilters.php</b>
├── <b>Front/</b>
└── <b>Admin/</b>
\--

Ή μπορείτε να τοποθετήσετε βοηθητικές κλάσεις όπως `LatteExtension.php` ή `TemplateFilters.php` στο φάκελο υποδομής `app/Core/Latte/`. Και τα συστατικά στον φάκελο `app/Components`. Η επιλογή εξαρτάται από τις συμβάσεις της ομάδας.


Μοντέλο - Η καρδιά της εφαρμογής .[#toc-model-heart-of-the-application]
=======================================================================

Το μοντέλο περιέχει όλη την επιχειρησιακή λογική της εφαρμογής. Για την οργάνωσή του, ισχύει ο ίδιος κανόνας - δομούμε με βάση τους τομείς:

/--pre
<b>app/Model/</b>
├── <b>Payment/</b>                   ← τα πάντα για τις πληρωμές
│   ├── <b>PaymentFacade.php</b>      ← κύριο σημείο εισόδου
│   ├── <b>PaymentRepository.php</b>
│   ├── <b>Payment.php</b>            ← οντότητα
├── <b>Order/</b>                     ← τα πάντα για τις παραγγελίες
│   ├── <b>OrderFacade.php</b>
│   ├── <b>OrderRepository.php</b>
│   ├── <b>Order.php</b>
└── <b>Shipping/</b>                  ← τα πάντα για τη ναυτιλία
\--

Στο μοντέλο, συναντάτε συνήθως αυτούς τους τύπους κλάσεων:

**Προσοψεις**: αντιπροσωπεύουν το κύριο σημείο εισόδου σε ένα συγκεκριμένο τομέα της εφαρμογής. Λειτουργούν ως ενορχηστρωτής που συντονίζει τη συνεργασία μεταξύ διαφορετικών υπηρεσιών για την υλοποίηση ολοκληρωμένων περιπτώσεων χρήσης (όπως "δημιουργία παραγγελίας" ή "επεξεργασία πληρωμής"). Κάτω από το επίπεδο ενορχήστρωσης, η όψη αποκρύπτει τις λεπτομέρειες υλοποίησης από την υπόλοιπη εφαρμογή, παρέχοντας έτσι μια καθαρή διεπαφή για την εργασία με τον συγκεκριμένο τομέα.

```php
class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// επικύρωση
		// δημιουργία παραγγελίας
		// αποστολή email
		// εγγραφή σε στατιστικά στοιχεία
	}
}
```

**Υπηρεσίες**: εστιάζουν σε συγκεκριμένες επιχειρηματικές λειτουργίες εντός ενός τομέα. Σε αντίθεση με τις προσόψεις που ενορχηστρώνουν ολόκληρες περιπτώσεις χρήσης, μια υπηρεσία υλοποιεί συγκεκριμένη επιχειρηματική λογική (όπως υπολογισμούς τιμών ή επεξεργασία πληρωμών). Οι υπηρεσίες είναι συνήθως χωρίς κατάσταση και μπορούν να χρησιμοποιηθούν είτε από τις προσόψεις ως δομικά στοιχεία για πιο σύνθετες λειτουργίες, είτε απευθείας από άλλα μέρη της εφαρμογής για απλούστερες εργασίες.

```php
class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// υπολογισμός τιμών
	}
}
```

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

```php
class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}
```

**Οντότητες**: αντικείμενα που αντιπροσωπεύουν τις κύριες επιχειρηματικές έννοιες της εφαρμογής, οι οποίες έχουν την ταυτότητά τους και αλλάζουν με την πάροδο του χρόνου. Συνήθως πρόκειται για κλάσεις που αντιστοιχίζονται σε πίνακες της βάσης δεδομένων με χρήση ORM (όπως το Nette Database Explorer ή το Doctrine). Οι οντότητες μπορούν να περιέχουν επιχειρηματικούς κανόνες σχετικά με τα δεδομένα τους και τη λογική επικύρωσης.

```php
// Οντότητα που αντιστοιχίζεται στον πίνακα παραγγελιών της βάσης δεδομένων
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}
```

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


Κώδικας υποδομής .[#toc-infrastructure-code]
============================================

Ο φάκελος `Core/` (ή επίσης `Infrastructure/`) φιλοξενεί τα τεχνικά θεμέλια της εφαρμογής. Ο κώδικας υποδομής συνήθως περιλαμβάνει:

/--pre
<b>app/Core/</b>
├── <b>Router/</b>               ← δρομολόγηση και διαχείριση URL
│   └── <b>RouterFactory.php</b>
├── <b>Security/</b>             ← αυθεντικοποίηση και εξουσιοδότηση
│   ├── <b>Authenticator.php</b>
│   └── <b>Authorizator.php</b>
├── <b>Logging/</b>              ← καταγραφή και παρακολούθηση
│   ├── <b>SentryLogger.php</b>
│   └── <b>FileLogger.php
├── <b>Cache/</b>                ← επίπεδο προσωρινής αποθήκευσης
│   └── <b>FullPageCache.php</b>
└── <b>Integration/</b>          ← ενσωμάτωση με πρόσθετες υπηρεσίες
	├── <b>Slack/</b>
	└── <b>Stripe/</b>
\--

Για μικρότερα έργα, μια επίπεδη δομή είναι φυσικά επαρκής:

/--pre
<b>Core/</b>
├── <b>RouterFactory.php</b>
├── <b>Authenticator.php</b>
└── <b>QueueMailer.php</b>
\--

Αυτός είναι κώδικας που:

- Χειρίζεται την τεχνική υποδομή (δρομολόγηση, καταγραφή, προσωρινή αποθήκευση).
- Ενσωματώνει εξωτερικές υπηρεσίες (Sentry, Elasticsearch, Redis)
- Παρέχει βασικές υπηρεσίες για ολόκληρη την εφαρμογή (αλληλογραφία, βάση δεδομένων)
- Είναι ως επί το πλείστον ανεξάρτητος από συγκεκριμένο τομέα - η κρυφή μνήμη ή ο καταγραφέας λειτουργεί το ίδιο για το ηλεκτρονικό εμπόριο ή το ιστολόγιο.

Αναρωτιέστε αν μια συγκεκριμένη κλάση ανήκει εδώ ή στο μοντέλο; Η βασική διαφορά είναι ότι ο κώδικας στο `Core/`:

- Δεν γνωρίζει τίποτα για τον τομέα (προϊόντα, παραγγελίες, άρθρα).
- Μπορεί συνήθως να μεταφερθεί σε άλλο έργο
- Λύνει το "πώς λειτουργεί" (πώς να στείλει το ταχυδρομείο), όχι το "τι κάνει" (τι ταχυδρομείο να στείλει)

Παράδειγμα για καλύτερη κατανόηση:

- `App\Core\MailerFactory` - δημιουργεί περιπτώσεις της κλάσης αποστολής ηλεκτρονικού ταχυδρομείου, χειρίζεται τις ρυθμίσεις SMTP
- `App\Model\OrderMailer` - χρησιμοποιεί το `MailerFactory` για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου σχετικά με τις παραγγελίες, γνωρίζει τα πρότυπα τους και πότε πρέπει να σταλούν


Σενάρια εντολών .[#toc-command-scripts]
=======================================

Οι εφαρμογές πρέπει συχνά να εκτελούν εργασίες εκτός των κανονικών αιτήσεων HTTP - είτε πρόκειται για επεξεργασία δεδομένων στο παρασκήνιο, είτε για συντήρηση, είτε για περιοδικές εργασίες. Για την εκτέλεση χρησιμοποιούνται απλά σενάρια στον κατάλογο `bin/`, ενώ η πραγματική λογική της εφαρμογής τοποθετείται στον κατάλογο `app/Tasks/` (ή `app/Commands/`).

Παράδειγμα:

/--pre
<b>app/Tasks/</b>
├── <b>Maintenance/</b>               ← σενάρια συντήρησης
│   ├── <b>CleanupCommand.php</b>     ← διαγραφή παλαιών δεδομένων
│   └── <b>DbOptimizeCommand.php</b>  ← βελτιστοποίηση βάσης δεδομένων
├── <b>Integration/</b>               ← ενσωμάτωση με εξωτερικά συστήματα
│   ├── <b>ImportProducts.php</b>     ← εισαγωγή από σύστημα προμηθευτή
│   └── <b>SyncOrders.php</b>         ← συγχρονισμός παραγγελιών
└── <b>Scheduled/</b>                 ← τακτικές εργασίες
	├── <b>NewsletterCommand.php</b>  ← αποστολή ενημερωτικών δελτίων
	└── <b>ReminderCommand.php</b>    ← ειδοποιήσεις πελατών
\--

Τι ανήκει στο μοντέλο και τι στα σενάρια εντολών; Για παράδειγμα, η λογική για την αποστολή ενός email ανήκει στο μοντέλο, η μαζική αποστολή χιλιάδων email ανήκει στο `Tasks/`.

Οι εργασίες [εκτελούνται |https://blog.nette.org/en/cli-scripts-in-nette-application] συνήθως [από τη γραμμή εντολών |https://blog.nette.org/en/cli-scripts-in-nette-application] ή μέσω cron. Μπορούν επίσης να εκτελούνται μέσω αίτησης HTTP, αλλά πρέπει να ληφθεί υπόψη η ασφάλεια. Ο παρουσιαστής που εκτελεί την εργασία πρέπει να είναι ασφαλής, π.χ. μόνο για συνδεδεμένους χρήστες ή με ένα ισχυρό token και πρόσβαση από επιτρεπόμενες διευθύνσεις IP. Για εργασίες μεγάλης διάρκειας, είναι απαραίτητο να αυξήσετε το χρονικό όριο της δέσμης ενεργειών και να χρησιμοποιήσετε το `session_write_close()` για να αποφύγετε το κλείδωμα της συνεδρίας.


Άλλοι πιθανοί κατάλογοι .[#toc-other-possible-directories]
==========================================================

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

/--pre
<b>app/</b>
├── <b>Api/</b>              ← Λογική API ανεξάρτητη από το επίπεδο παρουσίασης
├── <b>Database/</b>         ← σενάρια μετάβασης και σπορείς για δεδομένα δοκιμών
├── <b>Components/</b>       ← κοινά οπτικά στοιχεία σε όλη την εφαρμογή
├── <b>Event/</b>            ← χρήσιμη αν χρησιμοποιείται αρχιτεκτονική με γνώμονα το γεγονός
├── <b>Mail/</b>             ← πρότυπα ηλεκτρονικού ταχυδρομείου και σχετική λογική
└── <b>Utils/</b>            ← βοηθητικές κλάσεις
\--

Για κοινά οπτικά στοιχεία που χρησιμοποιούνται σε παρουσιαστές σε όλη την εφαρμογή, μπορείτε να χρησιμοποιήσετε το φάκελο `app/Components` ή `app/Controls`:

/--pre
<b>app/Components/</b>
├── <b>Form/</b>                 ← κοινά στοιχεία φόρμας
│   ├── <b>SignInForm.php</b>
│   └── <b>UserForm.php</b>
├── <b>Grid/</b>                 ← στοιχεία για τις λίστες δεδομένων
│   └── <b>DataGrid.php</b>
└── <b>Navigation/</b>           ← στοιχεία πλοήγησης
	├── <b>Breadcrumbs.php</b>
	└── <b>Menu.php</b>
\--

Εδώ ανήκουν τα στοιχεία με πιο σύνθετη λογική. Αν θέλετε να μοιράζεστε συστατικά μεταξύ πολλών έργων, είναι καλό να τα διαχωρίζετε σε ένα αυτόνομο πακέτο composer.

Στον κατάλογο `app/Mail` μπορείτε να τοποθετήσετε τη διαχείριση της επικοινωνίας μέσω ηλεκτρονικού ταχυδρομείου:

/--pre
<b>app/Mail/</b>
├── <b>templates/</b>            ← πρότυπα email
│   ├── <b>order-confirmation.latte</b>
│   └── <b>welcome.latte</b>
└── <b>OrderMailer.php</b>
\--


Χαρτογράφηση παρουσιαστή .[#toc-presenter-mapping]
==================================================

Η αντιστοίχιση ορίζει κανόνες για την εξαγωγή ονομάτων κλάσεων από ονόματα παρουσιαστών. Τους καθορίζουμε στη [διαμόρφωση |configuration] στο κλειδί `application › mapping`.

Σε αυτή τη σελίδα, έχουμε δείξει ότι τοποθετούμε τους παρουσιαστές στο φάκελο `app/Presentation` (ή `app/UI`). Πρέπει να ενημερώσουμε τη Nette για αυτή τη σύμβαση στο αρχείο διαμόρφωσης. Μια γραμμή είναι αρκετή:

```neon
application:
	mapping: App\Presentation\*\**Presenter
```

Πώς λειτουργεί η αντιστοίχιση; Για την καλύτερη κατανόηση, ας φανταστούμε πρώτα μια εφαρμογή χωρίς ενότητες. Θέλουμε οι κλάσεις παρουσιαστών να εμπίπτουν στο χώρο ονομάτων `App\Presentation`, έτσι ώστε ο παρουσιαστής `Home` να αντιστοιχίζεται στην κλάση `App\Presentation\HomePresenter`. Αυτό επιτυγχάνεται με αυτή τη διαμόρφωση:

```neon
application:
	mapping: App\Presentation\*Presenter
```

Η αντιστοίχιση λειτουργεί με την αντικατάσταση του αστερίσκου στη μάσκα `App\Presentation\*Presenter` με το όνομα του παρουσιαστή `Home`, με αποτέλεσμα το τελικό όνομα της κλάσης `App\Presentation\HomePresenter`. Απλό!

Ωστόσο, όπως βλέπετε σε παραδείγματα σε αυτό και σε άλλα κεφάλαια, τοποθετούμε κλάσεις παρουσιαστή σε επώνυμους υποκαταλόγους, για παράδειγμα ο παρουσιαστής `Home` αντιστοιχίζεται στην κλάση `App\Presentation\Home\HomePresenter`. Αυτό το επιτυγχάνουμε διπλασιάζοντας την άνω και κάτω τελεία (απαιτεί Nette Application 3.2):

```neon
application:
	mapping: App\Presentation\**Presenter
```

Τώρα θα προχωρήσουμε στην αντιστοίχιση των παρουσιαστών σε ενότητες. Μπορούμε να ορίσουμε συγκεκριμένη αντιστοίχιση για κάθε ενότητα:

```neon
application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter
```

Σύμφωνα με αυτή τη διαμόρφωση, ο παρουσιαστής `Front:Home` αντιστοιχίζεται στην τάξη `App\Presentation\Front\Home\HomePresenter`, ενώ ο παρουσιαστής `Api:OAuth` αντιστοιχίζεται στην τάξη `App\Api\OAuthPresenter`.

Επειδή οι ενότητες `Front` και `Admin` έχουν παρόμοια μέθοδο αντιστοίχισης και πιθανώς θα υπάρξουν περισσότερες τέτοιες ενότητες, είναι δυνατόν να δημιουργηθεί ένας γενικός κανόνας που θα τις αντικαθιστά. Ένας νέος αστερίσκος για την ενότητα θα προστεθεί στη μάσκα κλάσης:

```neon
application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter
```

Λειτουργεί επίσης για βαθύτερα φωλιασμένες δομές καταλόγων, όπως ο παρουσιαστής `Admin:User:Edit`, όπου το τμήμα με τον αστερίσκο επαναλαμβάνεται για κάθε επίπεδο και προκύπτει η κλάση `App\Presentation\Admin\User\Edit\EditPresenter`.

Ένας εναλλακτικός συμβολισμός είναι η χρήση ενός πίνακα που αποτελείται από τρία τμήματα αντί για μια συμβολοσειρά. Αυτή η σημειογραφία είναι ισοδύναμη με την προηγούμενη:

```neon
application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]
```

Δομή καταλόγου της εφαρμογής

Πώς να σχεδιάσετε μια σαφή και κλιμακούμενη δομή καταλόγου για έργα στο Nette Framework; Θα σας δείξουμε δοκιμασμένες πρακτικές που θα σας βοηθήσουν να οργανώσετε τον κώδικά σας. Θα μάθετε:

  • πώς να δομήσετε λογικά την εφαρμογή σε καταλόγους
  • πώς να σχεδιάζετε τη δομή ώστε να κλιμακώνεται καλά καθώς το έργο μεγαλώνει
  • ποιες είναι οι πιθανές εναλλακτικές λύσεις και τα πλεονεκτήματα ή μειονεκτήματά τους

Είναι σημαντικό να αναφέρουμε ότι το ίδιο το Nette Framework δεν επιμένει σε κάποια συγκεκριμένη δομή. Είναι σχεδιασμένο ώστε να προσαρμόζεται εύκολα σε οποιεσδήποτε ανάγκες και προτιμήσεις.

Βασική δομή έργου

Αν και το Nette Framework δεν υπαγορεύει κάποια σταθερή δομή καταλόγων, υπάρχει μια αποδεδειγμένη προεπιλεγμένη διάταξη με τη μορφή Web Project:

web-project/
├── app/              ← κατάλογος εφαρμογών
├── assets/           ← SCSS, αρχεία JS, εικόνες..., εναλλακτικά resources/
├── bin/              ← σενάρια γραμμής εντολών
├── config/           ← διαμόρφωση
├── log/              ← καταγεγραμμένα σφάλματα
├── temp/             ← προσωρινά αρχεία, κρυφή μνήμη
├── tests/            ← δοκιμές
├── vendor/           ← βιβλιοθήκες που εγκαθίστανται από τον Composer
└── www/              ← δημόσιος κατάλογος (document-root)

Μπορείτε να τροποποιήσετε ελεύθερα αυτή τη δομή σύμφωνα με τις ανάγκες σας – να μετονομάσετε ή να μετακινήσετε φακέλους. Στη συνέχεια, πρέπει απλώς να προσαρμόσετε τις σχετικές διαδρομές προς τους καταλόγους στο Bootstrap.php και ενδεχομένως στο composer.json. Δεν χρειάζεται τίποτα άλλο, ούτε πολύπλοκη αναδιαμόρφωση, ούτε συνεχείς αλλαγές. Το Nette διαθέτει έξυπνη αυτόματη ανίχνευση και αναγνωρίζει αυτόματα τη θέση της εφαρμογής, συμπεριλαμβανομένης της βάσης URL της.

Αρχές οργάνωσης κώδικα

Όταν εξερευνάτε για πρώτη φορά ένα νέο έργο, θα πρέπει να είστε σε θέση να προσανατολιστείτε γρήγορα. Φανταστείτε να κάνετε κλικ στον κατάλογο app/Model/ και να δείτε αυτή τη δομή:

app/Model/
├── Services/
├── Repositories/
└── Entities/

Από αυτήν, θα μάθετε μόνο ότι το έργο χρησιμοποιεί κάποιες υπηρεσίες, αποθετήρια και οντότητες. Δεν θα μάθετε τίποτα για τον πραγματικό σκοπό της εφαρμογής.

Ας δούμε μια διαφορετική προσέγγιση – οργάνωση με βάση τους τομείς:

app/Model/
├── Cart/
├── Payment/
├── Order/
└── Product/

Αυτό είναι διαφορετικό – με την πρώτη ματιά είναι σαφές ότι πρόκειται για ιστότοπο ηλεκτρονικού εμπορίου. Τα ίδια τα ονόματα των καταλόγων αποκαλύπτουν τι μπορεί να κάνει η εφαρμογή – λειτουργεί με πληρωμές, παραγγελίες και προϊόντα.

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

Χώροι ονομάτων

Είναι συμβατικό η δομή καταλόγου να αντιστοιχεί σε χώρους ονομάτων στην εφαρμογή. Αυτό σημαίνει ότι η φυσική θέση των αρχείων αντιστοιχεί στο χώρο ονομάτων τους. Για παράδειγμα, μια κλάση που βρίσκεται στο app/Model/Product/ProductRepository.php θα πρέπει να έχει χώρο ονομάτων App\Model\Product. Αυτή η αρχή βοηθά στον προσανατολισμό του κώδικα και απλοποιεί την αυτόματη φόρτωση.

Ενικός και πληθυντικός αριθμός στα ονόματα

Παρατηρήστε ότι χρησιμοποιούμε ενικό για τους κύριους καταλόγους εφαρμογών: app, config, log, temp, www. Το ίδιο ισχύει και στο εσωτερικό της εφαρμογής: Model, Core, Presentation. Αυτό συμβαίνει επειδή το καθένα αντιπροσωπεύει μια ενιαία έννοια.

Ομοίως, το app/Model/Product αντιπροσωπεύει τα πάντα σχετικά με τα προϊόντα. Δεν το ονομάζουμε Products επειδή δεν είναι ένας φάκελος γεμάτος προϊόντα (που θα περιείχε αρχεία όπως iphone.php, samsung.php). Είναι ένας χώρος ονομάτων που περιέχει κλάσεις για την εργασία με προϊόντα – ProductRepository.php, ProductService.php.

Ο φάκελος app/Tasks είναι στον πληθυντικό επειδή περιέχει ένα σύνολο αυτόνομων εκτελέσιμων σεναρίων – CleanupTask.php, ImportTask.php. Κάθε ένα από αυτά αποτελεί μια ανεξάρτητη μονάδα.

Για λόγους συνέπειας, συνιστούμε να χρησιμοποιείτε:

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

Δημόσιος κατάλογος www/

Αυτός ο κατάλογος είναι ο μόνος προσβάσιμος από το διαδίκτυο (το λεγόμενο document-root). Συχνά μπορεί να συναντήσετε το όνομα public/ αντί για www/ – είναι απλώς θέμα σύμβασης και δεν επηρεάζει τη λειτουργικότητα. Ο κατάλογος περιέχει:

  • Σημείο εισόδου της εφαρμογής index.php
  • αρχείο .htaccess με κανόνες mod_rewrite (για τον Apache)
  • Στατικά αρχεία (CSS, JavaScript, εικόνες)
  • Ανεβασμένα αρχεία

Για τη σωστή ασφάλεια της εφαρμογής, είναι ζωτικής σημασίας να έχετε ρυθμίσει σωστά το document-root.

Ποτέ μην τοποθετείτε το φάκελο node_modules/ σε αυτόν τον κατάλογο – περιέχει χιλιάδες αρχεία που μπορεί να είναι εκτελέσιμα και δεν πρέπει να είναι προσβάσιμα από το κοινό.

Κατάλογος εφαρμογών app/

Αυτός είναι ο κύριος κατάλογος με τον κώδικα της εφαρμογής. Βασική δομή:

app/
├── Core/               ← θέματα υποδομής
├── Model/              ← επιχειρησιακή λογική
├── Presentation/       ← παρουσιαστές και πρότυπα
├── Tasks/              ← σενάρια εντολών
└── Bootstrap.php       ← κλάση bootstrap της εφαρμογής

Bootstrap.php είναι η κλάση εκκίνησης της εφαρμογής που αρχικοποιεί το περιβάλλον, φορτώνει τη διαμόρφωση και δημιουργεί το δοχείο DI.

Ας δούμε τώρα αναλυτικά τους επιμέρους υποκαταλόγους.

Παρουσιαστές και πρότυπα

Έχουμε το τμήμα παρουσίασης της εφαρμογής στον κατάλογο app/Presentation. Μια εναλλακτική λύση είναι το σύντομο app/UI. Εκεί βρίσκονται όλοι οι παρουσιαστές, τα πρότυπα τους και τυχόν βοηθητικές κλάσεις.

Οργανώνουμε αυτό το επίπεδο ανά τομείς. Σε ένα σύνθετο έργο που συνδυάζει ηλεκτρονικό εμπόριο, blog και API, η δομή θα έμοιαζε ως εξής:

app/Presentation/
├── Shop/              ← frontend ηλεκτρονικού εμπορίου
│   ├── Product/
│   ├── Cart/
│   └── Order/
├── Blog/              ← blog
│   ├── Home/
│   └── Post/
├── Admin/             ← διαχείριση
│   ├── Dashboard/
│   └── Products/
└── Api/               ← Τελικά σημεία API
	└── V1/

Αντίθετα, για ένα απλό ιστολόγιο θα χρησιμοποιούσαμε αυτή τη δομή:

app/Presentation/
├── Front/             ← εμπρόσθιο άκρο ιστοσελίδας
│   ├── Home/
│   └── Post/
├── Admin/             ← διαχείριση
│   ├── Dashboard/
│   └── Posts/
├── Error/
└── Export/            ← RSS, sitemaps κ.λπ.

Οι φάκελοι όπως Home/ ή Dashboard/ περιέχουν παρουσιαστές και πρότυπα. Οι φάκελοι όπως Front/, Admin/ ή Api/ ονομάζονται modules. Τεχνικά, πρόκειται για κανονικούς καταλόγους που χρησιμεύουν για τη λογική οργάνωση της εφαρμογής.

Κάθε φάκελος με έναν παρουσιαστή περιέχει έναν παρουσιαστή με παρόμοιο όνομα και τα πρότυπά του. Για παράδειγμα, ο φάκελος Dashboard/ περιέχει:

Dashboard/
├── DashboardPresenter.php     ← παρουσιαστής
└── default.latte              ← πρότυπο

Αυτή η δομή καταλόγων αντικατοπτρίζεται στους χώρους ονομάτων των κλάσεων. Για παράδειγμα, το DashboardPresenter βρίσκεται στο χώρο ονομάτων App\Presentation\Admin\Dashboard (βλ. αντιστοίχιση παρουσιαστή):

namespace App\Presentation\Admin\Dashboard;

class DashboardPresenter extends Nette\Application\UI\Presenter
{
	//...
}

Αναφερόμαστε στον παρουσιαστή Dashboard μέσα στην ενότητα Admin στην εφαρμογή χρησιμοποιώντας τον συμβολισμό της άνω και κάτω τελείας ως Admin:Dashboard. Στη δράση του default στη συνέχεια ως Admin:Dashboard:default. Για ένθετες ενότητες χρησιμοποιούμε περισσότερες άνω και κάτω τελεία, για παράδειγμα Shop:Order:Detail:default.

Ανάπτυξη ευέλικτης δομής

Ένα από τα μεγάλα πλεονεκτήματα αυτής της δομής είναι το πόσο κομψά προσαρμόζεται στις αυξανόμενες ανάγκες του έργου. Ως παράδειγμα, ας πάρουμε το τμήμα που παράγει XML feeds. Αρχικά, έχουμε μια απλή φόρμα:

Export/
├── ExportPresenter.php   ← ένας παρουσιαστής για όλες τις εξαγωγές
├── sitemap.latte         ← πρότυπο για χάρτη σελίδων
└── feed.latte            ← πρότυπο για RSS feed

Με την πάροδο του χρόνου, προστίθενται περισσότεροι τύποι τροφοδοσίας και χρειαζόμαστε περισσότερη λογική για αυτούς… Κανένα πρόβλημα! Ο φάκελος Export/ γίνεται απλά μια ενότητα:

Export/
├── Sitemap/
│   ├── SitemapPresenter.php
│   └── sitemap.latte
└── Feed/
	├── FeedPresenter.php
	├── amazon.latte         ← feed για την Amazon
	└── ebay.latte           ← τροφοδοσία για το eBay

Απλά δημιουργήστε νέους υποφακέλους, χωρίστε τον κώδικα σε αυτούς και ενημερώστε τους συνδέσμους (π.χ. από Export:feed σε Export:Feed:amazon). Χάρη σε αυτό, μπορούμε να επεκτείνουμε σταδιακά τη δομή όπως χρειάζεται, το επίπεδο φωλιάσματος δεν περιορίζεται με κανέναν τρόπο.

Για παράδειγμα, αν στη διαχείριση έχετε πολλούς παρουσιαστές που σχετίζονται με τη διαχείριση παραγγελιών, όπως OrderDetail, OrderEdit, OrderDispatch κ.λπ. μπορείτε να δημιουργήσετε μια ενότητα (φάκελο) Order για καλύτερη οργάνωση, η οποία θα περιέχει (φακέλους για) τους παρουσιαστές Detail, Edit, Dispatch και άλλους.

Τοποθεσία προτύπου

Στα προηγούμενα παραδείγματα, είδαμε ότι τα πρότυπα βρίσκονται απευθείας στο φάκελο με τον παρουσιαστή:

Dashboard/
├── DashboardPresenter.php     ← παρουσιαστής
├── DashboardTemplate.php      ← προαιρετική κλάση προτύπου
└── default.latte              ← πρότυπο

Αυτή η τοποθεσία αποδεικνύεται η πιο βολική στην πράξη – έχετε όλα τα σχετικά αρχεία στη διάθεσή σας.

Εναλλακτικά, μπορείτε να τοποθετήσετε τα πρότυπα σε έναν υποφάκελο templates/. Η Nette υποστηρίζει και τις δύο παραλλαγές. Μπορείτε ακόμη και να τοποθετήσετε τα πρότυπα εντελώς εκτός του φακέλου Presentation/. Τα πάντα σχετικά με τις επιλογές τοποθέτησης προτύπων μπορείτε να βρείτε στο κεφάλαιο Αναζήτηση προτύπων.

Βοηθητικές κλάσεις και συστατικά

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

1. Απευθείας με τον παρουσιαστή σε περίπτωση που πρόκειται για συγκεκριμένα στοιχεία για τον συγκεκριμένο παρουσιαστή:

Product/
├── ProductPresenter.php
├── ProductGrid.php        ← συστατικό για την καταχώριση προϊόντων
└── FilterForm.php         ← φόρμα για φιλτράρισμα

2. Για την ενότητα – συνιστούμε τη χρήση του φακέλου Accessory, ο οποίος τοποθετείται τακτοποιημένα στην αρχή του αλφαβήτου:

Front/
├── Accessory/
│   ├── NavbarControl.php    ← συστατικά για frontend
│   └── TemplateFilters.php
├── Product/
└── Cart/

3. Για ολόκληρη την εφαρμογή – στον φάκελο Presentation/Accessory/:

app/Presentation/
├── Accessory/
│   ├── LatteExtension.php
│   └── TemplateFilters.php
├── Front/
└── Admin/

Ή μπορείτε να τοποθετήσετε βοηθητικές κλάσεις όπως LatteExtension.php ή TemplateFilters.php στο φάκελο υποδομής app/Core/Latte/. Και τα συστατικά στον φάκελο app/Components. Η επιλογή εξαρτάται από τις συμβάσεις της ομάδας.

Μοντέλο – Η καρδιά της εφαρμογής

Το μοντέλο περιέχει όλη την επιχειρησιακή λογική της εφαρμογής. Για την οργάνωσή του, ισχύει ο ίδιος κανόνας – δομούμε με βάση τους τομείς:

app/Model/
├── Payment/                   ← τα πάντα για τις πληρωμές
│   ├── PaymentFacade.php      ← κύριο σημείο εισόδου
│   ├── PaymentRepository.php
│   ├── Payment.php            ← οντότητα
├── Order/                     ← τα πάντα για τις παραγγελίες
│   ├── OrderFacade.php
│   ├── OrderRepository.php
│   ├── Order.php
└── Shipping/                  ← τα πάντα για τη ναυτιλία

Στο μοντέλο, συναντάτε συνήθως αυτούς τους τύπους κλάσεων:

Προσοψεις: αντιπροσωπεύουν το κύριο σημείο εισόδου σε ένα συγκεκριμένο τομέα της εφαρμογής. Λειτουργούν ως ενορχηστρωτής που συντονίζει τη συνεργασία μεταξύ διαφορετικών υπηρεσιών για την υλοποίηση ολοκληρωμένων περιπτώσεων χρήσης (όπως „δημιουργία παραγγελίας“ ή „επεξεργασία πληρωμής“). Κάτω από το επίπεδο ενορχήστρωσης, η όψη αποκρύπτει τις λεπτομέρειες υλοποίησης από την υπόλοιπη εφαρμογή, παρέχοντας έτσι μια καθαρή διεπαφή για την εργασία με τον συγκεκριμένο τομέα.

class OrderFacade
{
	public function createOrder(Cart $cart): Order
	{
		// επικύρωση
		// δημιουργία παραγγελίας
		// αποστολή email
		// εγγραφή σε στατιστικά στοιχεία
	}
}

Υπηρεσίες: εστιάζουν σε συγκεκριμένες επιχειρηματικές λειτουργίες εντός ενός τομέα. Σε αντίθεση με τις προσόψεις που ενορχηστρώνουν ολόκληρες περιπτώσεις χρήσης, μια υπηρεσία υλοποιεί συγκεκριμένη επιχειρηματική λογική (όπως υπολογισμούς τιμών ή επεξεργασία πληρωμών). Οι υπηρεσίες είναι συνήθως χωρίς κατάσταση και μπορούν να χρησιμοποιηθούν είτε από τις προσόψεις ως δομικά στοιχεία για πιο σύνθετες λειτουργίες, είτε απευθείας από άλλα μέρη της εφαρμογής για απλούστερες εργασίες.

class PricingService
{
	public function calculateTotal(Order $order): Money
	{
		// υπολογισμός τιμών
	}
}

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

class OrderRepository
{
	public function find(int $id): ?Order
	{
	}

	public function findByCustomer(int $customerId): array
	{
	}
}

Οντότητες: αντικείμενα που αντιπροσωπεύουν τις κύριες επιχειρηματικές έννοιες της εφαρμογής, οι οποίες έχουν την ταυτότητά τους και αλλάζουν με την πάροδο του χρόνου. Συνήθως πρόκειται για κλάσεις που αντιστοιχίζονται σε πίνακες της βάσης δεδομένων με χρήση ORM (όπως το Nette Database Explorer ή το Doctrine). Οι οντότητες μπορούν να περιέχουν επιχειρηματικούς κανόνες σχετικά με τα δεδομένα τους και τη λογική επικύρωσης.

// Οντότητα που αντιστοιχίζεται στον πίνακα παραγγελιών της βάσης δεδομένων
class Order extends Nette\Database\Table\ActiveRow
{
	public function addItem(Product $product, int $quantity): void
	{
		$this->related('order_items')->insert([
			'product_id' => $product->id,
			'quantity' => $quantity,
			'unit_price' => $product->price,
		]);
	}
}

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

Κώδικας υποδομής

Ο φάκελος Core/ (ή επίσης Infrastructure/) φιλοξενεί τα τεχνικά θεμέλια της εφαρμογής. Ο κώδικας υποδομής συνήθως περιλαμβάνει:

app/Core/
├── Router/               ← δρομολόγηση και διαχείριση URL
│   └── RouterFactory.php
├── Security/             ← αυθεντικοποίηση και εξουσιοδότηση
│   ├── Authenticator.php
│   └── Authorizator.php
├── Logging/              ← καταγραφή και παρακολούθηση
│   ├── SentryLogger.php
│   └── FileLogger.php
├── Cache/                ← επίπεδο προσωρινής αποθήκευσης
│   └── FullPageCache.php
└── Integration/          ← ενσωμάτωση με πρόσθετες υπηρεσίες
	├── Slack/
	└── Stripe/

Για μικρότερα έργα, μια επίπεδη δομή είναι φυσικά επαρκής:

Core/
├── RouterFactory.php
├── Authenticator.php
└── QueueMailer.php

Αυτός είναι κώδικας που:

  • Χειρίζεται την τεχνική υποδομή (δρομολόγηση, καταγραφή, προσωρινή αποθήκευση).
  • Ενσωματώνει εξωτερικές υπηρεσίες (Sentry, Elasticsearch, Redis)
  • Παρέχει βασικές υπηρεσίες για ολόκληρη την εφαρμογή (αλληλογραφία, βάση δεδομένων)
  • Είναι ως επί το πλείστον ανεξάρτητος από συγκεκριμένο τομέα – η κρυφή μνήμη ή ο καταγραφέας λειτουργεί το ίδιο για το ηλεκτρονικό εμπόριο ή το ιστολόγιο.

Αναρωτιέστε αν μια συγκεκριμένη κλάση ανήκει εδώ ή στο μοντέλο; Η βασική διαφορά είναι ότι ο κώδικας στο Core/:

  • Δεν γνωρίζει τίποτα για τον τομέα (προϊόντα, παραγγελίες, άρθρα).
  • Μπορεί συνήθως να μεταφερθεί σε άλλο έργο
  • Λύνει το „πώς λειτουργεί“ (πώς να στείλει το ταχυδρομείο), όχι το „τι κάνει“ (τι ταχυδρομείο να στείλει)

Παράδειγμα για καλύτερη κατανόηση:

  • App\Core\MailerFactory – δημιουργεί περιπτώσεις της κλάσης αποστολής ηλεκτρονικού ταχυδρομείου, χειρίζεται τις ρυθμίσεις SMTP
  • App\Model\OrderMailer – χρησιμοποιεί το MailerFactory για την αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου σχετικά με τις παραγγελίες, γνωρίζει τα πρότυπα τους και πότε πρέπει να σταλούν

Σενάρια εντολών

Οι εφαρμογές πρέπει συχνά να εκτελούν εργασίες εκτός των κανονικών αιτήσεων HTTP – είτε πρόκειται για επεξεργασία δεδομένων στο παρασκήνιο, είτε για συντήρηση, είτε για περιοδικές εργασίες. Για την εκτέλεση χρησιμοποιούνται απλά σενάρια στον κατάλογο bin/, ενώ η πραγματική λογική της εφαρμογής τοποθετείται στον κατάλογο app/Tasks/app/Commands/).

Παράδειγμα:

app/Tasks/
├── Maintenance/               ← σενάρια συντήρησης
│   ├── CleanupCommand.php     ← διαγραφή παλαιών δεδομένων
│   └── DbOptimizeCommand.php  ← βελτιστοποίηση βάσης δεδομένων
├── Integration/               ← ενσωμάτωση με εξωτερικά συστήματα
│   ├── ImportProducts.php     ← εισαγωγή από σύστημα προμηθευτή
│   └── SyncOrders.php         ← συγχρονισμός παραγγελιών
└── Scheduled/                 ← τακτικές εργασίες
	├── NewsletterCommand.php  ← αποστολή ενημερωτικών δελτίων
	└── ReminderCommand.php    ← ειδοποιήσεις πελατών

Τι ανήκει στο μοντέλο και τι στα σενάρια εντολών; Για παράδειγμα, η λογική για την αποστολή ενός email ανήκει στο μοντέλο, η μαζική αποστολή χιλιάδων email ανήκει στο Tasks/.

Οι εργασίες εκτελούνται συνήθως από τη γραμμή εντολών ή μέσω cron. Μπορούν επίσης να εκτελούνται μέσω αίτησης HTTP, αλλά πρέπει να ληφθεί υπόψη η ασφάλεια. Ο παρουσιαστής που εκτελεί την εργασία πρέπει να είναι ασφαλής, π.χ. μόνο για συνδεδεμένους χρήστες ή με ένα ισχυρό token και πρόσβαση από επιτρεπόμενες διευθύνσεις IP. Για εργασίες μεγάλης διάρκειας, είναι απαραίτητο να αυξήσετε το χρονικό όριο της δέσμης ενεργειών και να χρησιμοποιήσετε το session_write_close() για να αποφύγετε το κλείδωμα της συνεδρίας.

Άλλοι πιθανοί κατάλογοι

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

app/
├── Api/              ← Λογική API ανεξάρτητη από το επίπεδο παρουσίασης
├── Database/         ← σενάρια μετάβασης και σπορείς για δεδομένα δοκιμών
├── Components/       ← κοινά οπτικά στοιχεία σε όλη την εφαρμογή
├── Event/            ← χρήσιμη αν χρησιμοποιείται αρχιτεκτονική με γνώμονα το γεγονός
├── Mail/             ← πρότυπα ηλεκτρονικού ταχυδρομείου και σχετική λογική
└── Utils/            ← βοηθητικές κλάσεις

Για κοινά οπτικά στοιχεία που χρησιμοποιούνται σε παρουσιαστές σε όλη την εφαρμογή, μπορείτε να χρησιμοποιήσετε το φάκελο app/Components ή app/Controls:

app/Components/
├── Form/                 ← κοινά στοιχεία φόρμας
│   ├── SignInForm.php
│   └── UserForm.php
├── Grid/                 ← στοιχεία για τις λίστες δεδομένων
│   └── DataGrid.php
└── Navigation/           ← στοιχεία πλοήγησης
	├── Breadcrumbs.php
	└── Menu.php

Εδώ ανήκουν τα στοιχεία με πιο σύνθετη λογική. Αν θέλετε να μοιράζεστε συστατικά μεταξύ πολλών έργων, είναι καλό να τα διαχωρίζετε σε ένα αυτόνομο πακέτο composer.

Στον κατάλογο app/Mail μπορείτε να τοποθετήσετε τη διαχείριση της επικοινωνίας μέσω ηλεκτρονικού ταχυδρομείου:

app/Mail/
├── templates/            ← πρότυπα email
│   ├── order-confirmation.latte
│   └── welcome.latte
└── OrderMailer.php

Χαρτογράφηση παρουσιαστή

Η αντιστοίχιση ορίζει κανόνες για την εξαγωγή ονομάτων κλάσεων από ονόματα παρουσιαστών. Τους καθορίζουμε στη διαμόρφωση στο κλειδί application › mapping.

Σε αυτή τη σελίδα, έχουμε δείξει ότι τοποθετούμε τους παρουσιαστές στο φάκελο app/Presentationapp/UI). Πρέπει να ενημερώσουμε τη Nette για αυτή τη σύμβαση στο αρχείο διαμόρφωσης. Μια γραμμή είναι αρκετή:

application:
	mapping: App\Presentation\*\**Presenter

Πώς λειτουργεί η αντιστοίχιση; Για την καλύτερη κατανόηση, ας φανταστούμε πρώτα μια εφαρμογή χωρίς ενότητες. Θέλουμε οι κλάσεις παρουσιαστών να εμπίπτουν στο χώρο ονομάτων App\Presentation, έτσι ώστε ο παρουσιαστής Home να αντιστοιχίζεται στην κλάση App\Presentation\HomePresenter. Αυτό επιτυγχάνεται με αυτή τη διαμόρφωση:

application:
	mapping: App\Presentation\*Presenter

Η αντιστοίχιση λειτουργεί με την αντικατάσταση του αστερίσκου στη μάσκα App\Presentation\*Presenter με το όνομα του παρουσιαστή Home, με αποτέλεσμα το τελικό όνομα της κλάσης App\Presentation\HomePresenter. Απλό!

Ωστόσο, όπως βλέπετε σε παραδείγματα σε αυτό και σε άλλα κεφάλαια, τοποθετούμε κλάσεις παρουσιαστή σε επώνυμους υποκαταλόγους, για παράδειγμα ο παρουσιαστής Home αντιστοιχίζεται στην κλάση App\Presentation\Home\HomePresenter. Αυτό το επιτυγχάνουμε διπλασιάζοντας την άνω και κάτω τελεία (απαιτεί Nette Application 3.2):

application:
	mapping: App\Presentation\**Presenter

Τώρα θα προχωρήσουμε στην αντιστοίχιση των παρουσιαστών σε ενότητες. Μπορούμε να ορίσουμε συγκεκριμένη αντιστοίχιση για κάθε ενότητα:

application:
	mapping:
		Front: App\Presentation\Front\**Presenter
		Admin: App\Presentation\Admin\**Presenter
		Api: App\Api\*Presenter

Σύμφωνα με αυτή τη διαμόρφωση, ο παρουσιαστής Front:Home αντιστοιχίζεται στην τάξη App\Presentation\Front\Home\HomePresenter, ενώ ο παρουσιαστής Api:OAuth αντιστοιχίζεται στην τάξη App\Api\OAuthPresenter.

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

application:
	mapping:
		*: App\Presentation\*\**Presenter
		Api: App\Api\*Presenter

Λειτουργεί επίσης για βαθύτερα φωλιασμένες δομές καταλόγων, όπως ο παρουσιαστής Admin:User:Edit, όπου το τμήμα με τον αστερίσκο επαναλαμβάνεται για κάθε επίπεδο και προκύπτει η κλάση App\Presentation\Admin\User\Edit\EditPresenter.

Ένας εναλλακτικός συμβολισμός είναι η χρήση ενός πίνακα που αποτελείται από τρία τμήματα αντί για μια συμβολοσειρά. Αυτή η σημειογραφία είναι ισοδύναμη με την προηγούμενη:

application:
	mapping:
		*: [App\Presentation, *, **Presenter]
		Api: [App\Api, '', *Presenter]