Un’estensione Maho è un normale pacchetto Composer, ma svilupparla dentro lo store che la usa spinge
di solito verso due strade pessime: lavorare direttamente nel vendor/, dove il prossimo
composer update cancella le modifiche, oppure copiare i file avanti e indietro a mano. localdev/
chiude entrambe. Ogni pacchetto vive in una cartella sua, con un repository git separato dal root, e
Composer lo symlinka dentro vendor/. Il codice che scrivi è già quello che lo store esegue, senza
copie e senza reinstallare il progetto a ogni ritocco.
# senza localdev: lavori nel vendor e il prossimo update cancella le modifiche
vim vendor/acme/maho-example-shipping/Model/Shipping.php
# con localdev: lavori in localdev/, Composer lo symlinka in vendor/
vim localdev/maho-example-shipping/Model/Shipping.php
🔧 Nota di banco. Il cantiere è tuo: la cassetta degli attrezzi (
localdev/) sta accanto al magazzino (vendor/), ma i due non si mescolano. Pialli, incolli e ricominci senza toccare la merce già in vendita sullo scaffale.
Cosa serve
- Un progetto Maho con un ambiente funzionante. Nei nostri esempi usiamo Harbor, il launcher Docker Compose open source di MageMEGA, ispirato a Laravel Sail, descritto in Sviluppo locale di Maho con Harbor: se non lo conosci, parti da quell’articolo e poi torna qui.
- Docker con il plugin Compose v2, perché ogni comando passa da Harbor.
- Git, perché ogni pacchetto in
localdev/è un repository a sé, con il suo remote e la sua storia.
Quick start: dal pacchetto allo store
1. Dichiara il path repository
Dì a Composer che le cartelle di localdev/ sono una sorgente di pacchetti. Apri il composer.json
del project root e aggiungi (o verifica) questo blocco:
{
"repositories": [
{
"type": "path",
"url": "./localdev/*",
"options": {
"symlink": true
},
"canonical": true
}
]
}
type: "path"significa che la sorgente è una cartella locale, non Packagist.url: "./localdev/*"tratta ogni sottocartella come un pacchetto.symlink: truefa puntarevendor/<vendor>/<package>direttamente alla cartella inlocaldev/.canonical: truefa vincere questa sorgente su Packagist per un pacchetto con lo stesso nome.
2. Metti il pacchetto in localdev/
mkdir -p localdev
git clone [email protected]:acme/maho-example-shipping.git localdev/maho-example-shipping
Se parti da zero, crea lo scheletro dal template empiricompany/module-template (fork di
MahoCommerce/module-template) e inizializza il repository lì dentro. In entrambi i casi il
pacchetto ha il suo .git, i suoi remote e la sua storia: il root non lo considera una sottocartella
versionata.
3. Richiedilo in @dev
Un path repository non pubblica versioni taggate, quindi il pacchetto si risolve come versione di
sviluppo. Installalo con il vincolo @dev:
./vendor/bin/harbor composer require acme/maho-example-shipping:@dev
In alternativa scrivi a mano la dipendenza e aggiorna solo quella:
"require": {
"acme/maho-example-shipping": "@dev"
}
./vendor/bin/harbor composer update acme/maho-example-shipping
4. Installa le dipendenze del pacchetto ed esegui i gate
Il pacchetto ha un suo composer.json, con le proprie dipendenze di sviluppo e i propri script.
Gestiscilo con --working-dir, senza entrare nella cartella:
# prima volta: installa le dipendenze di sviluppo dentro il pacchetto
./vendor/bin/harbor composer --working-dir=localdev/maho-example-shipping install
# tutti i gate del pacchetto (test, lint, cs, phpstan, rector) prima di pushare
./vendor/bin/harbor composer --working-dir=localdev/maho-example-shipping check
--working-dir punta Composer alla cartella del pacchetto, mentre harbor va sempre lanciato dalla
root del progetto. check è l’alias definito nel composer.json del pacchetto: li dentro ci sono
tutti i controlli che il repository esegue in CI.
5. Applica il modulo allo store
./vendor/bin/harbor composer update # root: aggancia e aggiorna il pacchetto
./vendor/bin/harbor maho migrate # esegue gli installer del modulo
./vendor/bin/harbor maho cache:flush
Lo store ora vede l’estensione. migrate applica schema e dati dichiarati dal modulo; cache:flush
è obbligatorio dopo aver rinominato blocchi, handle di layout o alias di configurazione.
🔧 Nota di banco. Tra il banco e la vetrina c’è un
composer updatedalla root che collega il pacchetto allo store. È il momento in cui la panchina di lavoro si affaccia sulla bottega: da qui in poi ogni saldatura inlocaldev/è già visibile, senza ricopiare niente.
Come funziona
Il punto centrale è che vendor/<vendor>/<package> non è una copia del pacchetto: è un link.
ls -l vendor/acme/maho-example-shipping
# lrwxrwxrwx ... vendor/acme/maho-example-shipping -> ../../localdev/maho-example-shipping
Da qui tre conseguenze pratiche:
- Modifica e vedi subito. Il bootstrap di Maho carica le classi dal percorso dentro
vendor/, che è il link: una modifica inlocaldev/è già in esecuzione, senza reinstallare niente. - Repository separato.
localdev/raccoglie repository a sé, mentre il root resta il tuo progetto. Puoi versionare, ramificare e pushare il pacchetto senza toccare lo store, e viceversa. - Nessun inquinamento del vendor. Non lavori mai dentro
vendor/e non rischi di perdere le modifiche a ogni aggiornamento.
La parola @dev non è un vezzo: serve perché un path repository non espone versioni taggate. Il
pacchetto si risolve come dev e le sue modifiche vengono rilette a ogni composer update.
Lavoro quotidiano
Modificare il codice del modulo. Lavora direttamente in localdev/<package>: PHP,
etc/config.xml, layout, template e codice sotto app/code/ sono letti live dal symlink. Dopo un
cambio di nome a blocchi, alias o handle di layout, svuota la cache:
./vendor/bin/harbor maho cache:flush
Eseguire i gate del pacchetto. Sono gli stessi comandi del passo 4, e la sequenza giusta prima di
pushare è install la prima volta, poi check:
./vendor/bin/harbor composer --working-dir=localdev/maho-example-shipping check
Evita harbor composer exec --working-dir: fallisce con chdir(): No such file or directory.
Lancia il binario da uno script o direttamente.
Reinstradare lo store sul pacchetto. Dopo aver aggiunto o rinominato un pacchetto, o dopo aver cambiato i suoi asset:
./vendor/bin/harbor composer update
Verificare il risultato.
./vendor/bin/harbor maho migrate
./vendor/bin/harbor maho db:query "SELECT * FROM core_resource WHERE code LIKE '%example%'"
./vendor/bin/harbor maho cache:flush
Se il pacchetto spedisce comandi CLI, compaiono nella lista di Maho:
./vendor/bin/harbor maho list | grep example
L’eccezione: gli asset pubblicati in public/
Tutto quanto sopra vale per il codice PHP e per i file risolti live dal symlink. Gli asset sono
l’eccezione. Il maho-composer-plugin copia le cartelle public/, skin/ e js/ di ogni modulo
installato dentro l’albero public/ del progetto: qui non c’è nessun symlink, è una copia vera e
propria.
Conseguenze:
- Modificare un file sotto
localdev/<package>/public/non si propaga da solo. Rilanciacomposer updatedalla root (basta il solo pacchetto) per ripubblicarlo. - La pubblicazione copia soltanto, non elimina: se rinomini una cartella di asset, la vecchia copia
resta in
public/e va rimossa a mano.
# un file cambiato sotto public/ del pacchetto non è attivo finché non ripubblichi
./vendor/bin/harbor composer update acme/maho-example-shipping
Questo vale per skin, JavaScript e qualsiasi asset che il modulo spedisce. Per i temi c’è un percorso dedicato, che evita di ripubblicare a mano a ogni build.
Temi: copiare indietro con --pack
Quando il pacchetto è un tema, la build degli asset segue un flusso suo, perché gli stessi file vanno
sviluppati nella copia pubblicata e poi riportati nel repository. Il pacchetto tema riregistra il
comando core dev:frontend:theme:build e aggiunge l’opzione --pack, che copia i file dal progetto
al pacchetto (con prune) dopo la build. Viene dal repository utility
empiricompany/maho-theme-kit.
# lavora sugli asset nella copia pubblicata del progetto
./vendor/bin/harbor maho dev:frontend:theme:build --theme <pkg>/<theme> --watch
# oppure ricostruisci e riporta nel pacchetto a ogni modifica
./vendor/bin/harbor maho dev:frontend:theme:build --theme <pkg>/<theme> --watch --pack
# build minificata, prima di committare
./vendor/bin/harbor maho dev:frontend:theme:build --theme <pkg>/<theme> --pack
L’ordine conta: composer install/update sovrascrive le copie degli asset nel progetto, quindi
lancia build --pack prima di Composer, oppure non lanciare Composer mentre hai modifiche non
ancora riportate nel pacchetto. --pack salta i pacchetti installati come copia in vendor/ (una
dist, non un checkout di sviluppo): lì riportare i file sarebbe inutile.
🔧 Nota di banco. Il tema è il mobile che monti sulla vetrina: lo ceselli nella copia installata e, quando è finito,
--packlo riporta nello stampo. Per i moduli normali non serve un attrezzo ad hoc: basta uncomposer updateche rimetta in vetrina gli asset cambiati.
Gate e rilascio
Prima di pushare, la sequenza completa dentro il pacchetto:
./vendor/bin/harbor composer --working-dir=localdev/maho-example-shipping install
./vendor/bin/harbor composer --working-dir=localdev/maho-example-shipping check
check esegue gli script dichiarati nel composer.json del pacchetto: tipicamente test, lint,
cs (php-cs-fixer), phpstan e rector. Gli stessi controlli girano in GitHub Actions insieme a
composer validate --strict, ai controlli di sintassi e al gate sulle line ending. Quando sono
verdi:
git -C localdev/maho-example-shipping add -A
git -C localdev/maho-example-shipping commit -m "Add example shipping extension"
git -C localdev/maho-example-shipping push
Un tag di versione avvia la release quando il pacchetto ha il workflow dedicato. I gate provano stile e tipi, non il comportamento: dopo il push, prova a mano il flusso nello store (checkout, area clienti, admin) prima di considerarlo pronto.
🔧 Ultima nota di banco. Il banco è in ordine, gli attrezzi al loro posto e la vetrina è a un
composer updatedi distanza. Da qui puoi pubblicare il modulo, aprirne un altro accanto e tenere tutto separato nello stesso cantiere. Buon lavoro!
Troubleshooting
| Sintomo | Controlla |
|---|---|
| Composer non vede il pacchetto | il blocco repositories con url: "./localdev/*" nel root, poi composer update |
| Il pacchetto installato non è un symlink | options.symlink: true e che non ci sia una copia precedente in vendor/ |
| Le modifiche PHP non si vedono | cache:flush; per i rename di blocchi e alias la cache di layout/config serve ancora XML vecchio |
| Un asset cambiato non si aggiorna | ripubblica con composer update dalla root: è una copia, non un symlink |
| Resta una cartella asset orfana | la pubblicazione non elimina: rimuovila a mano da public/ |
harbor composer exec --working-dir fallisce |
non usarlo: lancia il binario tramite uno script |
| I gate passano ma lo store si rompe | i gate non provano il comportamento: test manuale del flusso |
Nota di redazione.Testo preparato con l'aiuto dell'AI e verificato sulla fonte prima della pubblicazione. Può contenere errori: se ne trovi uno, segnalalo.Leggi il Manifesto
Il tuo negozio vende ancora?
Raccontaci da dove parti: ti rispondiamo con una prima valutazione, senza impegno.
Chiedi l'analisi del negozio