Changelog

All notable changes to this project will be documented in this file.

[12.3.0] - 2026-09-09 - Crittografia Asimmetrica Completa in Encryptor (Chiavi, CSR, Certificati, Catena di Fiducia, Cifratura a Busta)

Minor release che estende Encryptor — finora limitato ad hash e cifratura simmetrica — con un set completo di primitive di crittografia asimmetrica: coppie di chiavi, richieste di firma certificato, certificati self-signed o firmati da CA (con controllo esplicito dell'estensione X.509v3 basicConstraints), verifica della catena di fiducia, firma/verifica di dati e cifratura a busta (envelope encryption). Le nuove funzioni sono generiche e riusabili da qualunque progetto basato sul framework, non legate a un caso d'uso applicativo specifico — non è stata rilasciata una prima versione ridotta (solo self-signed) perché, trattandosi di codice di libreria condiviso e non di un caso d'uso applicativo puntuale, un set di primitive incompleto (mancava, ad esempio, il corrispettivo di "firma" per la sola verifica del certificato, o l'analogo asimmetrico di encryptString()) avrebbe significato lasciare un lavoro a metà.

✨ Nuove Funzionalità

Core/HelperClasses/Encryptor — dieci nuovi metodi per crittografia asimmetrica reale (OpenSSL)

Aggiunti dieci metodi statici, nello stesso stile di quelli già esistenti (?Config $customConfig = null come ultimo parametro):

Chiavi e certificati:

Verifica:

Cifratura a busta:

Configurazione OpenSSL autosufficiente — nessun openssl.cnf di sistema richiesto

generateAsymmetricKeyPair(), generateCertificateSigningRequest(), generateSelfSignedCertificate() e signCertificateSigningRequest() passano tutte alle funzioni OpenSSL sottostanti l'opzione config, risolta da resolveOpensslConfigPath(): se Config::opensslConfigPath non è valorizzata, viene generato — una sola volta per processo, tramite tempnam(), ripulito a fine richiesta con register_shutdown_function() — un file di configurazione OpenSSL minimale (costante privata Encryptor::MINIMAL_OPENSSL_CONFIG) contenente le sezioni [req]/[req_distinguished_name] necessarie alle operazioni di base e le sezioni [v3_ca]/[v3_leaf] (rispettivamente basicConstraints critical,CA:TRUE/CA:FALSE) usate da generateSelfSignedCertificate()/signCertificateSigningRequest() per l'estensione X.509v3. Questo elimina la dipendenza da un openssl.cnf di sistema risolvibile automaticamente (assente per default su alcune installazioni PHP su Windows, dove altrimenti openssl_pkey_new()/openssl_csr_new()/openssl_csr_sign() falliscono silenziosamente ritornando false) — OPENSSL_CONFIG_PATH resta disponibile come override esplicito solo per esigenze avanzate (es. un provider/engine OpenSSL specifico).

Nuove proprietà di Config/costanti di Config/config.php (sezione "Encryptor Constants"):

CostanteProprietà ConfigTipoDefault
ASYMMETRIC_KEY_TYPEasymmetricKeyTypeintOPENSSL_KEYTYPE_RSA
ASYMMETRIC_KEY_BITSasymmetricKeyBitsint2048
ASYMMETRIC_DIGEST_ALGORITHMasymmetricDigestAlgorithmstring"sha256"
CERTIFICATE_VALIDITY_DAYScertificateValidityDaysint3650
OPENSSL_CONFIG_PATHopensslConfigPathstring"" (via getenv('OPENSSL_CONFIG_PATH'))

File modificati:

✅ Backward Compatibility


[12.2.1] - 2026-09-08 - Correzione Rilevamento Foreign Key Null in buildPropertiesConditions()

Patch che corregge un difetto di DependentModel::buildPropertiesConditions(), il metodo che traduce le proprietà passate ai finder magici generati dal framework (findByProperties(), le varianti countBy...()/deleteBy...() generate da __callStatic/__call, ecc.) in condizioni SQL: per una proprietà foreign key (dichiarata come sottoclasse di ReferencedEntity) valorizzata con null, il metodo generava una condizione sulla colonna sbagliata, priva del suffisso Id richiesto dalla convenzione di naming dell'ORM.

🐛 Bug Fix

Orm/ExtendedClasses/DependentModel::buildPropertiesConditions() — ricerca per foreign key null risolta sulla colonna sbagliata

Il quarto parametro di Query::appendCondition() (bool $foreignKey) indica all'adapter se il nome colonna va risolto con il suffisso Id (NotationManager::convertPropertyNameToColumnName($name, true), es. relatedEntityrelated_entity_id) invece che come proprietà scalare (relatedEntityrelated_entity). buildPropertiesConditions() determinava questo flag con $propertyValue instanceof ReferencedEntity — un controllo sul valore a runtime, non sul tipo dichiarato della proprietà. Per qualunque proprietà foreign key interrogata con valore null (il caso d'uso esplicito di una ricerca "dove la relazione è assente"), null instanceof ReferencedEntity è sempre false, indipendentemente dal tipo dichiarato della proprietà sull'entità: il metodo trattava quindi la colonna come se fosse scalare, generando una condizione WHERE related_entity IS NULL invece di WHERE related_entity_id IS NULL — una colonna che nella tabella non esiste, con conseguente errore SQL a runtime per qualunque ricerca su una foreign key nullable valorizzata a null.

buildPropertiesConditions() determina ora se una proprietà è una foreign key leggendo il tipo dichiarato tramite ReflectionProperty::getType() e is_subclass_of(..., ReferencedEntity::class), indipendentemente dal valore effettivo — corretto sia per il ramo null che per quello non-null, ed eliminando la necessità di istanziare una seconda volta ReflectionProperty nel ramo non-null (già usata per calcolare $bindTypes).

Nessun test esistente copriva questo percorso: gli unici test che invocano un finder con valore null su una proprietà foreign key (testMagicMethodCountByNullableEntityWithNullValue, testMagicMethodCountByMultiplePropertiesWithNullValues) verificano solo che appendCondition() venga chiamato, senza asserire il valore del quarto parametro.

File modificati:

✅ Backward Compatibility


[12.2.0] - 2026-09-05 - Rinominazione API di Localizator, Correzione TypeError e Nuovo Metodo getEnumerationLocaleAttribute()

Minor release che rinomina due metodi pubblici di Localizator i cui nomi non riflettevano più il tipo di ritorno effettivo (e, nel caso di getComposedEnumerationLocale(), erano diventati troppo simili al metodo privato sottostante), corregge un bug introdotto in 12.1.0, e aggiunge un nuovo metodo per leggere un singolo attributo da un valore di localizzazione composto.

♻️ Refactoring

Core/HelperClasses/Localizator — Rinominati getEnumerationLocaleArray() e getComposedEnumerationLocaleArray()

Entrambi i nomi risalivano a un paradigma precedente (commit 20eb7a15, dic 2024) in cui il primo metodo restituiva davvero un array; da allora restituisce una singola stringa (la label), ma il nome non era mai stato aggiornato. Con l'introduzione in 12.1.0 di un secondo metodo per il valore composto, la coppia di nomi (getEnumerationLocaleArray() → stringa, getComposedEnumerationLocaleArray() → array) era diventata fuorviante. Rinominati per riflettere il tipo di ritorno effettivo:

Il secondo nome evita deliberatamente la parola "Field/Fields": il metodo privato sottostante, getEnumerationLocaleField(), esegue il lookup grezzo (string|array) per un case dell'enum, mentre il metodo pubblico pretende e garantisce la forma composta (array). Chiamarlo getEnumerationLocaleFields() — come nome scelto in un primo momento — li rendeva praticamente indistinguibili a colpo d'occhio pur avendo contratti diversi (uno può restituire una stringa, l'altro lancia un'eccezione se non trova un array).

File modificati:

🐛 Bug Fix

Core/HelperClasses/Localizator::getEnumerationLocaleLabel()TypeError quando il valore di localizzazione dell'enum è un array

Da quando, in 12.1.0, getEnumerationLocaleField() è diventato string|array per supportare le chiavi di localizzazione composte, questo metodo (allora getEnumerationLocaleArray()) — dichiarato : string — continuava a restituire il valore così com'era. Se il case dell'enum ha un valore locale composto (es. {"label": "...", "description": "..."}), la chiamata sollevava TypeError: Return value must be of type string, array returned.

Il metodo ora verifica il tipo del valore restituito da getEnumerationLocaleField() e, se è un array, estrae la chiave 'label'.

File modificati:

Core/HelperClasses/Localizator::getComposedEnumerationLocale() — Errore nativo poco chiaro quando l'enum non ha un valore locale composto

Stesso difetto del punto precedente, presente fin dall'introduzione del metodo in 12.1.0 (allora getComposedEnumerationLocaleArray()): dichiarato : array, restituiva getEnumerationLocaleField() senza verificarne il tipo. Se il case dell'enum ha un valore locale semplice (stringa), la chiamata sollevava un TypeError a carico del tipo di ritorno.

Il metodo ora verifica il tipo del valore restituito da getEnumerationLocaleField() e, se non è un array, solleva una LocalizatorException esplicita — già prevista nel framework (Core/Exceptions/LocalizatorException.php) ma finora mai utilizzata — invece del TypeError/"illegal string offset" nativo di PHP.

File modificati:

✨ Nuove Funzionalità

Core/HelperClasses/Localizator::getEnumerationLocaleAttribute() — Lettura di un singolo attributo da un valore di localizzazione composto

Aggiunto un nuovo metodo pubblico che, dato un enum e il nome di un attributo, restituisce direttamente il valore di quell'attributo dal campo di localizzazione composto, senza dover passare per getComposedEnumerationLocale() e indicizzare manualmente l'array risultante. Internamente delega a getComposedEnumerationLocale(), ereditandone la LocalizatorException quando l'enum non ha un valore locale composto.

Il tipo di ritorno è string|array e non mixed: i file di locale sono file di linguaggio, il cui unico scopo è contenere testo (eventualmente organizzato in strutture annidate, come si vede in Aurunci/Application/Locales/it_IT.json con voci a più livelli, es. nobleQualification.don.male.label); un numero o un booleano al loro interno sarebbe un errore di contenuto, non un caso d'uso legittimo da rappresentare nel tipo. Poiché il framework non dichiara strict_types da nessuna parte, un simile errore verrebbe comunque coercito silenziosamente a stringa da PHP anziché sollevare un TypeError; il vantaggio pratico del tipo esplicito è invece sul lookup di una chiave $attribute inesistente, che con string|array (non nullable) fallisce subito con un TypeError invece di propagarsi silenziosamente come null.

File modificati:

📖 Documentazione

docs-phpdoc/ — Rigenerazione completa

Localizator è marcata @internal, quindi phpDocumentor non genera per essa (né per le altre classi @internal del framework) una pagina pubblica: le modifiche di questa release non compaiono nella documentazione generata. La rigenerazione ha comunque aggiornato 14 pagine rimaste indietro rispetto a modifiche di release precedenti mai propagate alla documentazione (es. Session per la 12.1.1, MultipleSelfReferencedEnumeration per la 12.1.2).

🔧 Manutenzione

phpunit.xml — Sostituito l'attributo deprecato cacheResult

PHPUnit 13 segnala cacheResult come deprecato e ne annuncia la rimozione in PHPUnit 14 (vendor/phpunit/phpunit/src/TextUI/Configuration/Xml/Loader.php), in favore di recordTestRunHistory (stesso significato, nuovo nome). Sostituito nell'attributo radice <phpunit>.

File modificati:

✅ Backward Compatibility


[12.1.2] - 2026-09-05 - Correzione Import Mancante in MultipleSelfReferencedEnumeration

Patch che corregge un fatal error latente nel trait MultipleSelfReferencedEnumeration: il metodo getChoiceByMultipleParent() dichiara il parametro Language $language, ma il file non importava la classe SismaFramework\Core\Enumerations\Language.

🐛 Bug Fix

Core/Traits/MultipleSelfReferencedEnumeration::getChoiceByMultipleParent()Language referenziata senza use, fatal error a runtime

Il parametro Language $language viene risolto da PHP, in assenza di un use esplicito, nel namespace corrente del file (SismaFramework\Core\Traits). Poiché la classe Language vive in SismaFramework\Core\Enumerations, qualsiasi enum concreto che utilizza il trait e invoca getChoiceByMultipleParent() solleva un Error: Class "SismaFramework\Core\Traits\Language" not found. Il difetto era presente fin dall'introduzione della firma tipizzata del metodo e non è mai emerso perché nessun test in Tests/ esercita questo trait.

Aggiunta la dichiarazione use SismaFramework\Core\Enumerations\Language; in testa al file.

File modificati:

✅ Backward Compatibility


[12.1.1] - 2026-08-27 - Correzione Bug in appendItem() e Rafforzamento della Sicurezza delle Sessioni

Patch che raggruppa un insieme di correzioni alla classe Session, emerse da una revisione mirata della gestione delle sessioni nel framework: un bug di ricorsione in appendItem() che ne vanificava silenziosamente il comportamento sulle chiavi annidate a più livelli, un disallineamento tra la durata del cookie di sessione e quella dei dati lato server, un confronto non timing-safe del token anti-hijacking, e una rotazione dell'ID di sessione ad ogni richiesta che non invalidava mai realmente l'ID precedente.

🐛 Bug Fix

Core/HelperClasses/Session::appendItemRecursive() — l'append su chiavi annidate a più livelli degradava silenziosamente a un overwrite

Per una chiave con un solo livello di annidamento (es. test[one]), appendItem() funziona correttamente: verifica se il valore è già presente nell'array di destinazione e, in caso contrario, lo accoda. Per una chiave con due o più livelli (es. test[one][two]), il ramo ricorsivo di appendItemRecursive() richiamava però setItemRecursive() invece di se stesso: il risultato, per qualunque profondità superiore a uno, era che il valore più profondo veniva sovrascritto come farebbe setItem() — perdendo l'array esistente, il controllo dei duplicati e la semantica di append che il metodo promette dalla sua firma. L'unico test esistente per l'append su chiavi annidate copriva un solo livello di profondità, motivo per cui il difetto è rimasto silente.

appendItemRecursive() richiama ora se stessa nel ramo ricorsivo, esattamente come già fanno setItemRecursive(), unsetItemRecursive(), getItemRecursive() e hasItemRecursive().

File modificati:

Core/HelperClasses/Session::start() — la sessione lato server poteva scadere prima del cookie del client

Il cookie di sessione viene emesso con lifetime: 3600 (un'ora), ma nessun punto del framework allineava session.gc_maxlifetime — il parametro PHP che determina quando i dati di sessione diventano eleggibili per la garbage collection — a quello stesso valore. Con l'ini di default di molte distribuzioni (tipicamente 1440 secondi, 24 minuti), i dati di sessione potevano essere ripuliti dal server ben prima della scadenza dichiarata al client: l'utente restava con un cookie ancora "valido" ma privo di sessione corrispondente, con un logout inatteso e difficile da riprodurre perché dipendente dalla configurazione dell'hosting.

start() imposta ora esplicitamente ini_set('session.gc_maxlifetime', 3600), in modo che la vita dei dati lato server sia sempre coerente con quella del cookie, indipendentemente dalla configurazione di php.ini dell'ambiente di destinazione.

File modificati:

🛡️ Sicurezza

Core/HelperClasses/Session::isValidSession() — confronto del token incoerente rispetto a start() e non timing-safe

start() calcola il token anti-hijacking con ($request->server['HTTP_USER_AGENT'] ?? null) . ($request->server['REMOTE_ADDR'] ?? null), tollerando l'assenza di uno dei due header. isValidSession() eseguiva lo stesso calcolo senza l'operatore di null coalescing, sollevando un warning "Undefined array key" nei contesti in cui uno dei due valori non è presente (client senza User-Agent, proxy che lo rimuovono, ambienti di test). Il confronto finale tra token atteso e token calcolato, inoltre, usava === anzichè un confronto a tempo costante, esponendo — sebbene in modo marginale, trattandosi di un hash SHA-512 non direttamente controllabile da un attaccante — a un timing attack in stile OWASP su una funzione dedicata proprio a rilevare sessioni dirottate.

isValidSession() è ora allineato a start() sull'uso di ?? null, e il confronto finale utilizza hash_equals() invece di ===.

File modificati:

Core/HelperClasses/Session::start() — l'ID di sessione precedente restava valido dopo la rotazione

start() viene invocato dal front controller (Public/index.php) ad ogni richiesta, e rigenera sempre l'ID di sessione tramite session_regenerate_id(). Chiamato senza l'argomento delete_old_session, però, il vecchio ID non viene invalidato: resta pienamente utilizzabile fino a quando non interviene la garbage collection di PHP (probabilistica, e ora fino a un'ora di distanza per effetto dell'allineamento di gc_maxlifetime sopra descritto). La rotazione ad ogni richiesta è una difesa in profondità riconosciuta contro il furto di sessione — riduce la finestra in cui un ID sottratto resta utile — ma senza l'invalidazione immediata del vecchio ID quel beneficio non si realizzava: l'ID rubato restava valido esattamente quanto se la rotazione non fosse mai avvenuta.

start() chiama ora session_regenerate_id(true), invalidando immediatamente l'ID precedente ad ogni rotazione.

File modificati:

🚀 Performance

Core/HelperClasses/Sessionpreg_match_all() eseguito anche per chiavi semplici senza annidamento

setItem(), getItem(), appendItem(), unsetItem() e hasItem() eseguivano incondizionatamente preg_match_all("/\\[([^\\]]*)\\]/", $key, $matches) per rilevare la sintassi di chiave annidata (foo[bar][baz]), anche quando la chiave passata era una semplice stringa priva di parentesi quadre — il caso di gran lunga più comune nell'uso tipico della classe.

Ciascuno dei cinque metodi esegue ora preg_match_all() solo se la chiave contiene effettivamente [, evitando il costo della regex nel percorso comune.

File modificati:

✅ Backward Compatibility


[12.1.0] - 2026-08-11 - Supporto Chiavi Enum Composte in Localizator

Minor release che estende Localizator con un nuovo metodo dedicato alla lettura di valori di localizzazione strutturati (array) per i case degli enum, mantenendo getEnumerationLocaleArray() invariato per l'uso corrente (label singola come stringa).

✨ Nuove Funzionalità

Core\HelperClasses\Localizator::getComposedEnumerationLocaleArray() — Lettura di chiavi di localizzazione composte per enum

Introdotto un nuovo metodo pubblico per leggere, per un case di enum, un valore di localizzazione strutturato come array (es. {"label": "...", "description": "..."}), accanto al metodo esistente getEnumerationLocaleArray() che continua a restituire una singola stringa.

Motivazione: getEnumerationLocaleArray() è tipizzato string ed è usato da SelectableEnumeration::getFriendlyLabel() per costruire le choice delle select — un ritorno string|array avrebbe costretto ogni chiamante a discriminare il tipo. Un metodo dedicato, sempre array, evita questo problema e resta esplicito su cosa aspettarsi.

Implementazione:

File modificati:

🧪 Test

Tests\Core\HelperClasses\LocalizatorTest — Copertura nuovo metodo

Aggiunto testGetComposedEnumerationLocaleArrayMethodExists(), sullo stesso modello del test esistente per getEnumerationLocaleArray().

✅ Backward Compatibility


[12.0.9] - 2026-08-11 - Correzione Firma getParent() in SelfReferencedEnumeration

Patch che corregge il contratto del trait SelfReferencedEnumeration, il cui metodo astratto getParent() era dichiarato con tipo di ritorno non nullable (self), impedendo di rappresentare correttamente la radice di una gerarchia di case enum.

🐛 Bug Fix

Core/Traits/SelfReferencedEnumeration::getParent() — impossibile rappresentare l'assenza di un padre

Un enum che implementa il trait per modellare una struttura gerarchica (analoga a una tabella auto-referenziata) ha sempre almeno un case radice privo di padre. Con getParent(): self, quel case è costretto a restituire un valore fittizio — tipicamente se stesso o un altro case a caso — perché il tipo di ritorno non ammette null. Il problema è lo stesso, concettualmente, di una foreign key auto-referenziata dichiarata NOT NULL.

Il metodo astratto è ora dichiarato getParent(): ?self, permettendo alle implementazioni di restituire null per il case radice.

File modificati:

✅ Backward Compatibility


[12.0.8] - 2026-08-05 - Ripristino della Propagazione dell'Istanza Debugger Condivisa

Patch che corregge un difetto architetturale introdotto con la conversione di Debugger da classe statica a classe di istanza (11.0.0), per cui la maggior parte dei punti che dipendono da DebuggerBaseForm, BaseController, Dispatcher, ControllerFactory, RenderService::generateView() e BaseAdapter — riceveva silenziosamente un'istanza nuova e isolata (tramite il valore di default new Debugger()) invece dell'istanza effettivamente in uso per la richiesta corrente (creata in Public/index.php e propagata tramite Dispatcher/ControllerFactory fino al controller).

🐛 Bug Fix

Core/HelperClasses/Debugger — nessun punto di accesso condiviso all'istanza della richiesta corrente

Ogni classe che dipende da Debugger lo riceve come parametro di costruttore/metodo con valore di default new Debugger() (reso possibile dal supporto di PHP 8.1 a new negli inizializzatori). Quando il chiamante omette il parametro — la norma, non l'eccezione, in tutto il codice applicativo esistente: nessun esempio in Sample/, TestsApplication/, nei template di scaffolding o nella documentazione passa esplicitamente l'istanza corretta a RenderService::generateView() — ciascuna di queste classi finisce con una propria istanza isolata. BaseController riceve correttamente l'istanza condivisa tramite ControllerFactory (che la inietta esplicitamente via reflection), ma quell'istanza non arriva mai a RenderService::generateView() (dove la debug bar viene effettivamente generata), né a BaseAdapter/DataMapper (dove vengono conteggiate le query eseguite), né a BaseForm (dove isValid() chiama setFormFilter()), perché nessuno di questi punti la passa esplicitamente. L'effetto pratico, mai notato perché silenzioso: la debug bar non ha mai mostrato dati reali in nessuno scenario realistico.

Debugger espone ora getInstance()/setInstance()/resetInstance(), sul modello già in uso in Config e RenderService. I punti di wiring elencati sotto sostituiscono il parametro Debugger $debugger = new Debugger() con ?Debugger $debugger = null e delegano il fallback a Debugger::getInstance() nel corpo del metodo — necessario perché PHP consente new ma non chiamate a metodi statici arbitrari come valore di default di un parametro. La dependency injection resta intatta: chi passa già un'istanza esplicita (o un mock, nei test) continua a poterlo fare esattamente come prima.

File modificati:

✅ Backward Compatibility


[12.0.7] - 2026-08-01 - Correzione Gestione Valori Non Stringa in Filter::customFilter()

Patch che corregge un difetto di Filter::customFilter(), l'unico metodo della classe Filter che passava il valore da validare direttamente a una funzione nativa di stringa (preg_match()) senza verificarne preventivamente il tipo, a differenza di tutti gli altri metodi (isString(), isEmail(), isSecurePassword(), ecc.) che già gestiscono in modo uniforme i valori non stringa restituendo false.

🐛 Bug Fix

Core/HelperClasses/Filter::customFilter() — valori non stringa passati direttamente a preg_match()

customFilter() dichiara $value come mixed ma lo passa senza controlli al parametro $subject di preg_match(), tipizzato internamente come string non nullable. Quando questo filtro viene collegato tramite FilterType::customFilter a un campo opzionale di un BaseForm (ad esempio uno slug che rispetta un pattern di caratteri consentiti), FormValidator valorizza il dato a validare con null per un campo lasciato vuoto e lo passa comunque al filtro: preg_match() riceve quindi direttamente null, che PHP 8.1+ segnala come deprecato e può risultare in un comportamento non controllato, invece del fallimento di validazione pulito (false) che ci si aspetterebbe in analogia con gli altri metodi della classe.

customFilter() ora verifica esplicitamente is_string($value) prima di invocare preg_match(), e confronta l'esito con === 1 invece di affidarsi alla conversione implicita int→bool del valore restituito da preg_match() (che vale anche false in caso di espressione regolare non valida). Qualunque valore non stringa, incluso null, produce ora un fallimento di validazione esplicito e coerente con il resto della classe.

File modificati:

✅ Backward Compatibility


[12.0.6] - 2026-07-23 - Correzione Corruzione Slug Numerici nel Dispatch verso CallableController

Patch che corregge un difetto nell'inoltro delle richieste verso i controller che implementano CallableController (routing dinamico via __call()), per cui la porzione di slug numerica-con-trattini di un URL veniva irrimediabilmente corrotta prima di raggiungere il metodo magico del controller.

🐛 Bug Fix

Core/HelperClasses/Dispatcher::executeCallableController() — slug con parti numeriche separate da trattino decodificati in modo errato

executeCallableController() invocava sempre il controller usando parsedAction, la versione dello slug convertita in camelCase da NotationManager::convertToCamelCase() (pensata per i nomi di metodo reali, non per i segmenti di uno slug arbitrario). Per uno slug come slug-with-2000-01-01-day, la conversione produce slugWith20000101Day: i trattini tra cifre vengono rimossi senza lasciare alcun separatore, rendendo impossibile ricostruire a valle il valore originale (2000-01-01 diventa indistinguibile da 20000101). Poiché il controller non definisce un metodo reale con quel nome, la chiamata ricadeva su __call(), che riceveva quindi il nome già corrotto.

executeCallableController() ora chiama il controller con parsedAction solo se corrisponde a un metodo realmente definito sulla classe (ReflectionClass::hasMethod()); altrimenti utilizza pathAction, il segmento di URL originale, non trasformato — lo stesso valore già usato da isCallableController() per la verifica di compatibilità tramite checkCompatibility(), ripristinando la coerenza tra i due controlli.

File modificati:

✅ Backward Compatibility


[12.0.5] - 2026-07-17 - Correzione Permessi Cache/Logs/filesystemMedia Mai Applicati in Installazione

Patch che corregge un difetto della procedura di installazione (sisma install) per cui le cartelle scrivibili a runtime (Cache, Logs, filesystemMedia) non ricevevano mai i permessi di scrittura previsti dal codice, lasciando l'applicazione priva di accesso in scrittura subito dopo l'installazione.

🐛 Bug Fix

Console/Services/Installation/InstallationManager — permessi 0777 su Cache, Logs, filesystemMedia mai applicati

install() chiama createProjectStructure(), che creava già tutte e cinque le cartelle previste (Config, Public, Cache, Logs, filesystemMedia) con mkdir(..., 0755, true). La successiva chiamata a createAdditionalFolders() — pensata apposta per portare Cache, Logs e filesystemMedia a 0777, le uniche tre che l'applicazione deve poter scrivere a runtime, a differenza di Config e Public — eseguiva mkdir()/chmod() solo if (!is_dir($path)): condizione sempre falsa, perché le tre cartelle esistevano già per effetto di createProjectStructure(). Il blocco pensato per garantire la scrittura era quindi sempre inattivo, codice morto fin dalla sua introduzione.

L'effetto pratico: subito dopo l'installazione, Cache/Logs/filesystemMedia restavano a 0755, di proprietà dell'utente che ha eseguito sisma install — tipicamente diverso dall'utente sotto cui gira il server web — impedendo a quest'ultimo di scrivere cache, log e media fino a un eventuale intervento correttivo esterno alla procedura di installazione stessa.

createProjectStructure() ora crea solo Config e Public; la creazione di Cache, Logs e filesystemMedia resta di esclusiva competenza di createAdditionalFolders(), che a questo punto le trova sempre assenti ed esegue realmente mkdir(0777, true) + chmod(0777).

File modificati:

✅ Backward Compatibility


[12.0.4] - 2026-07-14 - Buffer-Involucro per il Recovery degli Errori Residui in Sviluppo

Patch che completa la correzione avviata in 12.0.3. La guardia headers_sent() introdotta in quella release per evitare la cascata di pagine d'errore duplicate in produzione impediva, come effetto collaterale non voluto, anche la visualizzazione della pagina di dettaglio per i warning residui in ambiente di sviluppo: da quando RenderService scarica realmente l'output a fine rendering (12.0.2), all'avvio della shutdown function registrata da registerNonThrowableErrorHandler() (che PHP esegue solo alla terminazione effettiva dello script) gli header risultavano ormai quasi sempre già inviati per qualunque richiesta completata con successo, facendo uscire la guardia prima ancora di controllare l'ambiente. Il risultato era la contraddizione esplicitamente esclusa dal changelog 12.0.3: un warning residuo, anche in sviluppo, non generava più visibleError.

🐛 Bug Fix

Core/HelperClasses/ErrorHandler::registerNonThrowableErrorHandler() — warning residui non mostravano più la pagina di dettaglio in sviluppo

La causa non era l'ordine dei controlli nella shutdown function, ma il fatto che il contenuto della risposta fosse già stato scaricato per davvero (quindi irrecuperabile) ben prima che la shutdown function avesse la possibilità di intervenire. PHP mantiene invece aperti i buffer di output durante l'esecuzione delle shutdown function, scaricandoli automaticamente solo al termine di tutte — la stessa proprietà su cui si basava implicitamente il comportamento (corretto) precedente alla chiusura esplicita del buffer introdotta in 12.0.2.

registerNonThrowableErrorHandler() apre ora un buffer-involucro (ob_start() grezzo, non tracciato da BufferManager) non appena viene invocato, prima ancora di registrare la shutdown function: essendo aperto fuori dal livello base tracciato da BufferManager, i normali cicli start()/flush() di RenderService — inclusi quelli innescati da rendering completati con successo — vi trasferiscono il contenuto senza mai scaricarlo realmente all'esterno, lasciandolo recuperabile fino alla reale terminazione dello script. Quando la shutdown function deve sostituire integralmente la risposta (sempre in sviluppo; solo per errori realmente fatali in produzione, dopo la consueta verifica headers_sent()), il nuovo metodo BufferManager::discardAll() scarta anche questo involucro — cosa che il precedente clear(), vincolato a non retrocedere sotto il livello base per non toccare buffer esterni al framework, non poteva fare — e resetta il livello base, cosicché il rendering della pagina d'errore che segue venga effettivamente consegnato come unico output della richiesta.

File modificati:

Core/HelperClasses/Dispatcher::run() — warning di deprecazione residuo su QUERY_STRING assente

Portato alla luce testando la correzione precedente: strlen($this->request->server['QUERY_STRING']) generava un warning di deprecazione (strlen(): Passing null...) ogni volta che la richiesta non aveva query string e la chiave non risultava valorizzata nell'array $_SERVER (caso riscontrato con il server PHP integrato). Il warning, residuo a fine richiesta, veniva finora silenziato dal difetto descritto sopra; una volta corretto quest'ultimo, avrebbe fatto scattare la pagina di dettaglio ad ogni richiesta priva di query string.

File modificati:

📖 Documentazione

docs-phpdoc/ — Rigenerazione completa

Rigenerata tramite composer phpdoc per riflettere il nuovo metodo BufferManager::discardAll().

✅ Backward Compatibility


[12.0.3] - 2026-07-04 - Correzione Ordine http_response_code()/Flush e Cascata di Pagine d'Errore Duplicate

Patch che corregge due difetti resi visibili dalla chiusura esplicita del buffer introdotta in 12.0.2: impostare il codice di risposta HTTP dopo aver già scaricato l'output al client, e una shutdown function che non distingueva un errore fatale reale da un semplice warning residuo né verificava se una risposta fosse già stata inviata.

🐛 Bug Fix

Core/Services/RenderServicehttp_response_code() impostato dopo l'invio dell'output

Da quando BufferManager::flush() chiude realmente tutti i livelli di buffer del framework fino al floor (12.0.2), invocarlo durante il rendering scarica per davvero il contenuto al client, gli header compresi. generateView(), generateData() e generateJson() costruivano però l'oggetto Response — il cui costruttore chiama http_response_code() per impostare il codice desiderato (es. 500 per una pagina d'errore) — solo dopo aver chiamato BufferManager::flush(). A quel punto gli header erano già stati inviati, e PHP generava il warning http_response_code(): Cannot set response code - headers already sent (output started at .../BufferManager.php:55).

Spostata la costruzione di Response prima di BufferManager::flush() in tutti e tre i metodi: il codice di stato viene ora impostato mentre l'output è ancora bufferizzato, prima che gli header vengano effettivamente inviati.

File modificati:

Core/HelperClasses/ErrorHandler::registerNonThrowableErrorHandler() — cascata di pagine d'errore duplicate

Il warning descritto sopra, in quanto ultimo errore PHP della richiesta, veniva intercettato da error_get_last() nella shutdown function registrata da registerNonThrowableErrorHandler(). Questa si limitava a verificare is_array($error), senza distinguere un errore realmente fatale da un semplice warning né controllare se una risposta fosse già stata inviata per la richiesta corrente: trattava quindi il warning come un crash non catturato e tentava di renderizzare un'ulteriore pagina d'errore completa, la quale — generando a sua volta lo stesso warning — poteva ripetere la cascata una terza volta, questa volta mostrata "nuda" da PHP perché display_errors era già stato riattivato da showErrorInDevelopmentEnvironment() (chiamato nel blocco finally di Public/index.php, eseguito prima della shutdown function).

Aggiunte due guardie alla shutdown function:

In ambiente di sviluppo il comportamento resta volutamente invariato: qualsiasi errore residuo, warning incluso, continua a generare la pagina di dettaglio (nonThrowableError()), scelta progettuale per rendere visibile ogni anomalia durante lo sviluppo.

File modificati:

♻️ Pulizia Codice

Core/HelperClasses/Dispatcher/ControllerFactory::createController() — rimossa condizione "fast path" ridondante

Il metodo selezionava un "fast path" (new $controllerClassName($this->dataMapper, $this->debugger)) per i costruttori senza parametri, con un solo parametro DataMapper, o con DataMapper seguito da Debugger. Per tutti e tre i casi, il percorso generico resolveConstructorArguments() produce però esattamente lo stesso risultato: PHP ignora silenziosamente gli argomenti posizionali passati in eccesso rispetto ai parametri dichiarati da un costruttore, quindi passare sempre $dataMapper/$debugger a un costruttore che ne dichiara meno (o nessuno) equivaleva già a quanto fa resolveConstructorArguments() valutando i tipi dichiarati uno per uno. La condizione non copriva inoltre alcun caso non già gestito dal percorso generico: era codice morto.

Rimossa la condizione: createController() passa ora sempre attraverso resolveConstructorArguments().

File modificati:

📖 Documentazione

docs-phpdoc/ — Rigenerazione completa

Rigenerata tramite composer phpdoc. Nessuna modifica di rilievo: le correzioni di questa release non toccano segnature pubbliche né aggiungono classi, l'unico file con differenze (SismaFramework-Core-Services-RenderService.html) riflette esclusivamente lo spostamento dei numeri di riga dovuto al riordino del codice in RenderService.php.

✅ Backward Compatibility


[12.0.2] - 2026-07-04 - Chiusura Esplicita del Buffer di Rendering in RenderService

Patch che rende esplicita la chiusura del buffer di output aperto da RenderService durante il rendering, invece di fare affidamento sullo scaricamento implicito a fine script. La correzione ha permesso di eliminare un workaround diventato necessario nella suite di test e ha reso visibili due difetti preesistenti che il workaround mascherava.

🐛 Bug Fix

Core/Services/RenderService — Buffer di rendering non chiuso esplicitamente

assemblesComponents() (usato da generateView() e generateData()) e generateJson() aprono un livello di buffer tramite BufferManager::start() ma non lo richiudevano mai esplicitamente: il contenuto renderizzato restava bufferizzato fino alla chiusura naturale dello script PHP, che scarica automaticamente i buffer residui a fine richiesta. Questo comportamento implicito funziona in un normale ciclo richiesta/risposta (un processo per richiesta), ma lascia il buffer indefinitamente "in sospeso" in qualunque contesto che riutilizzi lo stesso processo per più cicli logici — come la suite di test, dove PHPUnit esegue centinaia di test nello stesso processo.

Aggiunta una chiamata esplicita a BufferManager::flush() al termine di generateView(), generateData() e generateJson(), subito prima di restituire la Response: il buffer aperto da ciascuna chiamata viene ora sempre chiuso nello stesso punto logico in cui viene aperto, senza fare affidamento sullo scaricamento implicito di fine script. Valutata anche l'aggiunta della stessa chiamata in Dispatcher::run(): verificato che è superflua, perché BufferManager::start() viene invocato solo da RenderService, che ora si chiude sempre da solo.

File modificati:

🧪 Test

Allineamento della suite dei test alla nuova gestione del buffer

Con BufferManager::flush() ora invocato esplicitamente da RenderService, la suite di test ha smesso di richiedere il workaround manuale (\ob_end_clean() a inizio test) precedentemente necessario per compensare il buffer di rendering mai chiuso. Rimosse le chiamate manuali a \ob_end_clean() in DispatcherTest, RenderTest e RenderServiceTest, e unificate le chiamate multiple a expectOutputRegex() per singolo test (non supportate da PHPUnit oltre la prima) in un'unica regex con lookahead.

La correzione ha inoltre reso visibili due difetti preesistenti, prima mascherati dal workaround:

File modificati:

✅ Backward Compatibility


[12.0.1] - 2026-07-03 - Fix: Copertura Incompleta Comando sisma upgrade (11.x → 12.0.0) e Miglioramenti al Sito di Autopromozione

🐛 Bug Fix

Estensione copertura comando sisma upgrade (11.x → 12.0.0)

La strategy Upgrade11to12Strategy copriva solo 2 delle 3 breaking change della 12.0.0 (rinomina SelfReferencedModel e riordino parametri di setFulltextIndexColumn), lasciando silenziosa la rimozione dei metodi deprecati e delle classi LogException/NoLogException.

File aggiunti:

File modificati:

robots.txt e sitemap.xml non raggiungibili (404) sul sito di autopromozione

Le richieste a /robots.txt e /sitemap.xml restituivano la pagina di errore generica del sito (Errore seguito dal path grezzo) invece del contenuto atteso. I due file erano posizionati nella root del modulo Sample/, ma ResourceHandler::handleResourceFile() risolve i file statici solo in tre percorsi: la root di progetto, gli assets strutturali del framework e la cartella Assets/ del modulo applicativo (Sample/Assets/) — mai la root del modulo stesso, che è riservata all'albero di classi/viste (Controllers/, Models/, Views/, ecc.). Non trovando il file in nessuno dei tre percorsi, il dispatcher lanciava una PageNotFoundException con il path richiesto come messaggio, gestita da SampleController::error() come una pagina di errore qualsiasi.

File spostati:

Config/config.php — La root del sito (/) serviva la pagina degli esempi invece della homepage

DEFAULT_PATH era impostato su sample: RouteResolver::parsePath() lo usa come controller quando l'URL è vuoto, quindi ogni richiesta a / risolveva a SampleController::index() (la lista di articoli demo) invece che a HomeController::index() (la landing page del framework). HomeController::welcome(), il cui docblock dichiara URL: /, era di fatto irraggiungibile da quel path — mentre sitemap.xml e robots.txt indicavano / come homepage con priorità massima.

File modificati:

Core/HelperClasses/Dispatcher/ControllerFactory.php — Istanziazione errata dei controller con parametri di costruttore aggiuntivi dopo DataMapper

createController() decideva se usare la "fast path" di istanziazione (new $controllerClassName($this->dataMapper, $this->debugger)) controllando solo che il primo parametro del costruttore fosse di tipo DataMapper, senza verificare che il secondo fosse effettivamente Debugger. Per un controller con firma __construct(DataMapper $dataMapper, XxxService $xxxService) — come Sample/Controllers/HomeController.php, DocsController.php e SampleController.php, che accettano FrameworkInfoService come secondo parametro — veniva comunque intrapresa la fast path, passando un'istanza di Debugger al posto del servizio atteso: il dispatcher reale falliva con un TypeError all'istanziazione del controller.

Il controllo ora enumera esplicitamente i soli casi ammessi per la fast path: costruttore senza parametri, un solo parametro di tipo DataMapper, oppure esattamente due parametri rispettivamente DataMapper e Debugger. Qualsiasi altra combinazione (inclusi eventuali costruttori con tre o più parametri) passa sempre dalla risoluzione generica resolveConstructorArguments(), che istanzia ogni parametro in base al proprio tipo — più robusta anche di un controllo basato solo su "il secondo parametro è Debugger?", che avrebbe comunque richiamato la fast path (con soli due argomenti posizionali) anche in presenza di un eventuale terzo parametro obbligatorio.

File modificati:

Sample/Controllers/SampleController::protected() — Metodi inesistenti su Authentication

L'azione chiamava $auth->isLogged(), $auth->getAuthenticatedUser() e $auth->getUserIdentifier() su Security\HttpClasses\Authentication: nessuno di questi tre metodi esiste, né su quella classe né sulla base BaseAuthentication (che espone solo getAuthenticableInterface(), utilizzabile esclusivamente nella stessa richiesta in cui checkAuthenticable() ha già validato le credenziali). Qualunque richiesta a /sample/protected falliva con un Error: Call to undefined method. Mancava inoltre la view sample/protected.php, mai creata.

Come documentato in docs/security.md, Authentication si occupa solo della validazione delle credenziali in fase di login; la persistenza dello stato di autenticazione tra richieste va gestita tramite Session. Riscritta l'azione per verificare Session::hasItem('userId'), coerente con l'esempio di login già presente nella documentazione. Il modulo demo non include un flusso di login (nessuna entity implementa AuthenticableInterface), quindi l'azione dimostra il pattern di guardia ma reindirizza sempre alla pagina di errore in assenza di sessione.

File aggiunti:

File modificati:

Core/HelperClasses/BufferManagerclear()/flush() non gestivano output buffer annidati

clear() e flush() operavano solo sul livello di output buffer più interno (ob_clean()/ob_flush() agiscono su un singolo livello), mentre diverse view che si appoggiano a siteLayout.php aprono un proprio buffer annidato (ob_start() per catturare $content prima di includere il layout condiviso) dentro quello già aperto da RenderService::assemblesComponents(). Se un'eccezione (ogni BaseException, incluse tutte le eccezioni HTTP del framework, chiama BufferManager::clear() nel proprio costruttore) veniva sollevata mentre il buffer della view era ancora aperto, clear() ripuliva solo quel livello interno, lasciando aperto e non azzerato il livello esterno del framework — una correttezza che si è retta finora solo sul fatto che quel livello esterno risultava sempre vuoto in pratica, non su una garanzia del codice.

clear()/flush() ora svuotano in loop tutti i livelli di buffer aperti dal framework, tramite ob_end_clean()/ob_end_flush(). Per evitare di richiudere buffer non di proprietà del framework (es. quello di PHPUnit durante i test, o un eventuale buffer aperto dal server prima dell'avvio della richiesta), la classe memorizza ora un livello di base ($baseLevel), rilevato al primo utilizzo, sotto il quale i due metodi non scendono mai. Una prima versione che svuotava incondizionatamente fino al livello 0 assoluto è stata scartata perché rompeva l'isolamento dei buffer di PHPUnit (57 test segnalati come "risky" nella suite completa).

File modificati:

📖 Documentazione

docs-phpdoc/ — Rigenerazione completa

Rigenerata tramite composer phpdoc per includere le nuove classi introdotte in questa release (ExceptionBaseClassTransformer, DeprecatedMethodUsageTransformer) e riflettere le modifiche a Upgrade11to12Strategy e UpgradeManager. Sample/, Config/ e Tests/ restano esclusi dalla generazione (phpdoc.xml), quindi le modifiche al sito di autopromozione e alla configurazione di questa release non compaiono in questa documentazione API.

🎨 Miglioramenti Sito di Autopromozione (Sample)

Rimosso l'uso di ob_start()/ob_get_clean() dalle view (commonParts/siteLayout.php sostituito da header/footer)

Le view basate sul layout condiviso (home/*, docs/*, quasi tutte le sample/*) catturavano il proprio contenuto con ob_start()/ob_get_clean() in una variabile $content, poi interpolata a metà di siteLayout.php tra navbar e footer — un buffer annidato dentro quello già aperto da RenderService::assemblesComponents(). Non è il pattern documentato dal framework: docs/views.md indica esplicitamente di dividere il layout in header.php/footer.php e usare require sequenziali, proprio per evitare qualunque trucco di output buffering nelle view. Le view più vecchie del sito (error.php, notify.php) seguivano già correttamente questo pattern.

Sample/Views/commonParts/siteLayout.php è stato sostituito da siteLayoutHeader.php (tutto fino alla chiusura della navbar) e siteLayoutFooter.php (dal footer alla chiusura di </html>). Le 12 view interessate ora fanno require del primo prima del contenuto e del secondo dopo, senza alcun output buffering proprio. Verificato con rendering reale (PHP built-in server) di tutte le pagine coinvolte, oltre alla suite PHPUnit completa.

File aggiunti:

File rimossi:

File modificati:

Versione e data di rilascio visibili nel sito

La versione corrente del framework e la data di rilascio non erano visibili in nessuna pagina del sito di autopromozione (Sample), se non nel CHANGELOG.md. Aggiunto un badge versione in navbar (linkato a /docs/changelog) e una riga "Versione X.Y.Z — rilasciata il ..." nel footer; corretto anche il campo softwareVersion nel JSON-LD della homepage, rimasto hardcoded a 10.0.3 per diverse major.

Le informazioni vengono ricavate automaticamente da composer.json (versione) e dal primo blocco datato del CHANGELOG.md (data di rilascio corrispondente), tramite un nuovo Service iniettato nei controller — coerente con il pattern di dependency injection già usato per DataMapper/Debugger — evitando così di doverle allineare manualmente ad ogni rilascio.

File aggiunti:

File modificati:

Rimozione codice di debug residuo

Rimossi gli script usati per diagnosticare il bug del parser Markdown risolto in 11.8.1, mai ripuliti dopo il fix, incluso un metodo di controller raggiungibile pubblicamente via URL.

File rimossi:

File modificati:

Sample/module.json — Allineamento versione

framework_version e requires.sismaframework erano rimasti fermi a 11.0.0; allineati a 12.0.1.

File modificati:

Sample/Assets/sitemap.xml — Allineamento con le pagine reali del sito

La sitemap indicizzava solo 2 delle 31 pagine di documentazione (getting-started, api-reference), non includeva /docs/changelog, ed elencava tutte le pagine con <lastmod> fermo al 2025-01-15. Riscritta per includere l'intero albero di docs/index.md, raggruppato per sezione con priorità decrescente, e aggiornate le date.

File modificati:

✅ Backward Compatibility

[12.0.0] - 2026-07-02 - Breaking Changes: Rinomina SelfReferencedModel e Riordinamento Parametri Query

💥 Breaking Changes

SelfReferencedModelSelfDependentModel

La classe SelfReferencedModel è stata rinominata in SelfDependentModel per allineare la nomenclatura del layer model al concetto di dipendenza (anziché referenziazione). Nel layer model la chiave classificatoria è la presenza di chiavi esterne nell'entità gestita (dipendenza), non il fatto di essere referenziata da altre entità (referenziazione, concetto proprio del layer entity). L'autoreferenziazione è bidirezionale, quindi un'entità autoreferenziata è anche autodipendente; il nome SelfDependentModel riflette correttamente la prospettiva del layer model.

File modificati:

Migrazione: Sostituire ogni occorrenza di SelfReferencedModel con SelfDependentModel nei use statement e nelle dichiarazioni extends dei propri model.

Query::setFulltextIndexColumn() — Riordinamento parametri

I parametri del metodo setFulltextIndexColumn della classe Query sono stati riordinati per garantire coerenza con la firma degli altri metodi della classe.

File modificati:

Migrazione: Verificare e aggiornare l'ordine degli argomenti in ogni chiamata a setFulltextIndexColumn.

Rimozione metodi e classi deprecati

Come pianificato, i metodi e le classi deprecati con rimozione prevista in questa versione sono stati eliminati.

DependentModel — rimossi i metodi deprecati dalla v10.1.0:

SelfDependentModel — rimossi i metodi deprecati dalla v10.1.0:

Security/ExtendedClasses/LogException e Security/ExtendedClasses/NoLogException — rimosse interamente, deprecate dalla v11.0.0. AccessDeniedException ora estende direttamente BaseException implementando ShouldBeLoggedException.

File modificati:

File rimossi:

Migrazione: Sostituire ogni chiamata ai metodi rimossi con l'equivalente metodo magico; sostituire extends LogException/extends NoLogException con extends BaseException (implementando ShouldBeLoggedException se la classe deve essere loggata).

✨ Nuove Funzionalità

Supporto upgrade automatico 11.x → 12.x nel comando sisma upgrade

Il sistema di upgrade automatico dei moduli ora copre anche la migrazione dalla versione 11 alla 12. Le due breaking change di questa versione vengono gestite tramite due nuovi transformer, integrati nella nuova strategy Upgrade11to12Strategy.

Transformer ClassRenameTransformer (confidence: 95%):

Transformer FulltextIndexColumnTransformer (confidence: 70%):

File aggiunti:

File modificati:

Console/Traits/InteractiveInputTrait — Iniezione dello stream di input per i test

Il trait espone ora il metodo pubblico setInputStream(), che permette di sostituire lo stream php://stdin usato da ask(), askConfirmation() e askSecret() con uno stream arbitrario (es. php://memory), rendendo testabili i comandi interattivi senza dover simulare un vero input da terminale. Lo stream, se non iniettato esplicitamente, viene aperto una sola volta e riutilizzato tra le chiamate successive (in precedenza veniva aperto e richiuso ad ogni singola richiesta).

File modificati:

🔧 Pulizia Configurazione

Config/config.php e Core/HelperClasses/Config.php — Rimozione costanti non utilizzate

Rimosse le costanti dichiarate in Config/config.php (e le relative proprietà esposte da Core/HelperClasses/Config.php) non più referenziate da alcuna classe del framework: ADAPTERS, ADAPTER_NAMESPACE, ADAPTER_PATH, CONFIGURATION_PASSWORD, CORE, CORE_NAMESPACE, CORE_PATH, DEFAULT_CONTROLLER, DEFAULT_CONTROLLER_NAMESPACE, DEFAULT_CONTROLLER_PATH, DEFAULT_META_URL, MODEL_PATH, ORM, ORM_NAMESPACE, ORM_PATH, PUBLIC_PATH, STRUCTURAL_RESOURCES_PATH, THIS_DIRECTORY.

Rimosse anche LOG_WARNING_ROW e LOG_DANGER_ROW: non erano lette da alcuna classe del framework (che usa invece LOG_VERBOSE_ACTIVE, LOG_DEVELOPMENT_MAX_ROW e LOG_PRODUCTION_MAX_ROW per la rotazione dei log), ma solo da moduli applicativi esterni per colorare un indicatore nella dashboard di back-end. La loro dichiarazione va spostata nel file di configurazione del modulo consumatore, secondo il pattern già seguito da costanti equivalenti (es. soglie di warning/danger per la dimensione dei media).

File modificati:

Migrazione: chi facesse riferimento diretto a una di queste costanti tramite \Config\NOME_COSTANTE in un modulo applicativo deve dichiararla nel file di configurazione del proprio modulo.


[11.9.0] - 2026-06-27 - Configurazione Database e Crittografia tramite Variabili d'Ambiente

Questa minor release permette di configurare le credenziali del database e la passphrase di cifratura tramite variabili d'ambiente, evitando di doverle scrivere come valori letterali in Config/configFramework.php — file che, a differenza di Config/config.php, viene generato durante l'installazione e tipicamente committato nel progetto applicativo. L'installer rileva automaticamente quando queste variabili sono già presenti nell'ambiente e salta la richiesta interattiva/CLI corrispondente.

✨ Nuove Funzionalità

Config/config.php — Costanti database e cifratura risolte tramite getenv()

Le costanti DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_PORT ed ENCRYPTION_PASSPHRASE non sono più dichiarate con const ma con define(__NAMESPACE__ . '\NOME_COSTANTE', getenv('NOME_COSTANTE') ?: ""). Questo era necessario perché PHP non ammette chiamate a funzione (getenv()) all'interno di un'espressione costante dichiarata con const; define() accetta invece un'espressione valutata a runtime. Il namespace va qualificato esplicitamente (__NAMESPACE__ . '\...') perché, a differenza di const, define() non eredita automaticamente il namespace del file.

Se la variabile d'ambiente non è impostata, il fallback resta la stringa vuota "", identico al valore di default precedente: il comportamento per chi non adotta variabili d'ambiente è invariato.

File modificati:

Console/Commands/InstallationCommand::collectDatabaseConfiguration() — Skip automatico con variabili d'ambiente già presenti

Aggiunto il metodo privato hasDatabaseConfigFromEnvironment(), che verifica se almeno una tra DATABASE_HOST, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_PORT è già impostata nell'ambiente del processo. Il controllo viene eseguito dopo quello sulle opzioni CLI (--db-host e affini, che restano prioritarie se esplicitamente fornite) e prima del prompt interattivo: se l'ambiente fornisce già la configurazione, l'installer stampa un messaggio informativo e prosegue senza chiedere nulla, evitando di scrivere un fallback letterale — e quindi un potenziale segreto — in Config/configFramework.php.

Aggiornato anche il testo di --help del comando per documentare il nuovo comportamento.

File modificati:

.env.example — Documentazione delle variabili d'ambiente consultate dal framework

Aggiunto un file .env.example nella root del framework che elenca le sei variabili d'ambiente lette via getenv() in Config/config.php. Il file è puramente documentale: il framework non effettua alcun parsing di file .env (nessuna nuova dipendenza, nessun loader interno) — le variabili devono essere rese disponibili come variabili d'ambiente del processo PHP da chi gestisce il deployment (Docker env_file, direttive del web server, systemd EnvironmentFile, export manuale, ecc.).

File creati:

🔧 Modifiche Interne

Console/Services/Installation/InstallationManager::updateConfigFile() — Supporto al nuovo pattern define()+getenv()

La sostituzione dei valori raccolti da CLI/prompt interattivo in Config/configFramework.php riconosceva solo il pattern const NOME = "valore";. Aggiunto un secondo pattern che riconosce define(__NAMESPACE__ . '\NOME', getenv('NOME') ?: "valore") e aggiorna soltanto il valore di fallback dopo ?:, lasciando intatta la chiamata a getenv() — così l'ambiente continua ad avere priorità anche su un valore scritto in fase di installazione.

File modificati:

✅ Backward Compatibility


[11.8.1] - 2026-06-25 - Correzione Parsing Code Block Annidati nelle Blockquote (Sample)

Patch che corregge il parser Markdown del sito di autopromozione/documentazione (Sample/Controllers/DocsController.php): i fenced code block annidati in una blockquote venivano interpretati in modo scorretto, corrompendo il rendering di tutto il contenuto successivo del documento. Corretto anche un fence orfano nel file docs/advanced-orm.md.

🐛 Bug Fixes

Sample/Controllers/DocsController::parseMarkdown() — Code block annidati in blockquote

La regex che estrae i fenced code block (sequenza di tre backtick) cercava i marcatori ovunque nel testo, senza considerare un eventuale prefisso > di blockquote. Quando un code block era annidato in una blockquote (es. una riga > seguita dai tre backtick e dal nome del linguaggio), il marcatore di chiusura — anch'esso prefissato da > — non veniva riconosciuto come tale: la regex continuava ad espandersi non-greedy fino ai successivi tre backtick "nudi" (non prefissati da > ) nel documento, inghiottendo tutto il contenuto intermedio (inclusi altri code block, header e liste) in un unico blocco corrotto con lingua errata.

Aggiunto un nuovo step, eseguito prima dell'estrazione dei code block "normali" e dello step delle blockquote, che riconosce specificamente i fenced code block prefissati da > su ogni riga (apertura, contenuto e chiusura), rimuove il prefisso dal contenuto e li converte in <pre><code> esattamente come gli altri code block. Le righe di blockquote senza code block annidato continuano a essere gestite dallo step esistente.

File modificati:

docs/advanced-orm.md — Fence di chiusura orfano

Un marcatore di chiusura (tre backtick) senza apertura corrispondente era stato lasciato per errore dopo una lista (sezione "Best Practices" del capitolo sul lazy loading). Da quel punto in poi, l'alternanza apertura/chiusura dei fence successivi nel documento risultava sfasata, producendo blocchi di codice corrotti che inghiottivano header e sezioni successive.

File modificati:

✅ Backward Compatibility


[11.8.0] - 2026-06-23 - Miglioramento Flessibilità addRequest() in BaseForm e Standardizzazione Formattazione

Questa minor release estende la flessibilità del metodo addRequest() della classe BaseForm permettendo il controllo esplicito sulle sovrascritture di valori nella request. Inoltre, standardizza la formattazione del codice con miglior indentazione e trailing comma secondo le best practice PHP moderne.

✨ Nuove Funzionalità

Core/BaseClasses/BaseForm::addRequest() — Controllo esplicito sulle sovrascritture

Il metodo addRequest() è stato esteso con due miglioramenti:

Ampliamento tipi di $value:

Consente di iniettare nella request dati di diversi tipi primitivi, non solo stringhe e array.

Aggiunta parametro $override con controllo sulle sovrascritture:

Il parametro $override = true (default) mantiene il comportamento precedente: sovrascrive sempre. Passando false, il metodo scrive il valore solo se la proprietà non esiste ancora in request->input.

Caso d'uso:

// Inietta un valore di default che non sovrascrive l'input dell'utente
$this->addRequest('email', 'default@example.com', override: false);

File modificati:

🎨 Miglioramenti Formattazione Codice

Standardizzazione Indentazione e Trailing Comma

Standardizzate le convenzioni di formattazione secondo PSR-12:

Costruttore di BaseForm:

Metodi con parametri multipli:

Vantaggi della trailing comma:

File modificati:

🧪 Test

Tests/Core/BaseClasses/BaseFormTest — Copertura nuovo parametro $override

Aggiunti tre test per verificare il comportamento del nuovo parametro $override in addRequest():

File creati:

File modificati:

✅ Backward Compatibility


[11.7.0] - 2026-06-18 - Opzione --module e Discovery dei Moduli Non Configurati

Introduce l'opzione --module=NomeModulo per selezionare esplicitamente quale modulo deve gestire un comando quando più moduli registrano lo stesso nome. Aggiunge contestualmente la discovery automatica dei moduli fisicamente presenti su filesystem ma non ancora dichiarati in MODULE_FOLDERS, risolvendo il problema di bootstrap circolare per cui un modulo non poteva registrarsi tramite il proprio comando di installazione perché non ancora configurato.

✨ Nuove Funzionalità

Console/HelperClasses/CommandDispatcher — Opzione --module per selezione esplicita del modulo

Aggiunta l'opzione globale opzionale --module=NomeModulo al dispatcher dei comandi console. Quando specificata, il dispatcher filtra la lista dei comandi compatibili e ne esegue solo uno appartenente al modulo indicato; se nessun comando di quel modulo è compatibile, viene lanciata RuntimeException come per un comando sconosciuto. Senza l'opzione il comportamento rimane invariato (primo match vince, nell'ordine di priorità della discovery).

Per supportare il filtro, ogni comando scoperto viene ora associato al proprio modulo di appartenenza tramite un array parallelo $commandModules[]. La firma di discoverFromDirectory() include il parametro string $module e addCommandStrategy() accetta un secondo parametro opzionale string $module = '' per retrocompatibilità con i chiamanti esistenti.

File modificati:

Console/HelperClasses/CommandDispatcher — Discovery automatica dei moduli non configurati

Il metodo privato discoverUnconfiguredModules() esegue un glob su {rootPath}/*/Console/Commands/ e restituisce le cartelle modulo che hanno quella struttura ma non sono ancora presenti in MODULE_FOLDERS. Questi moduli vengono scansionati dopo quelli configurati e prima del framework, con priorità inferiore rispetto ai moduli dichiarati nella configurazione. La combinazione con --module= permette di eseguire il comando di installazione di un modulo anche prima che sia stato aggiunto a MODULE_FOLDERS.

File modificati:

🧪 Test

Tests/Console/HelperClasses/CommandDispatcherTest — Copertura opzione --module e moduli non configurati

File modificati:

✅ Backward Compatibility


[11.6.3] - 2026-06-17 - Correzione Input Password e Sostituzione Valori Numerici nel File di Configurazione

Patch che corregge due bug nel comando di installazione CLI: askSecret() emetteva un errore stty su ambienti senza TTY reale (IDE, pipe, container), e updateConfigFile() troncava i valori numerici come la porta del database a causa di un'ambiguità nelle backreference PCRE della stringa di sostituzione.

🐛 Bug Fixes

Console/Traits/InteractiveInputTrait — Errore stty su stdin non-TTY

Il metodo askSecret() chiamava system('stty -echo') condizionato solo al check PHP_OS === 'WIN', che non copre i casi in cui stdin non è un terminale reale anche su Linux/macOS (IDE come VS Code o PhpStorm, esecuzione via pipe, container Docker, ambienti CI). In questi contesti stty stampava l'errore stty: 'standard input': Inappropriate ioctl for device subito dopo il prompt della password.

Il check è stato sostituito con stream_isatty($handle), che verifica correttamente se il file descriptor è collegato a un TTY reale indipendentemente dal sistema operativo. Se stdin non è un TTY, la password viene letta senza tentare di disabilitare l'echo.

File modificati:

Console/Services/Installation/InstallationManager — Troncamento valori numerici in updateConfigFile()

Il metodo updateConfigFile() costruiva la stringa di sostituzione per preg_replace() interpolando il valore direttamente in una stringa PHP: "$1$2{$value}$2". Quando $value iniziava con una cifra (es. la porta 3306), l'interpolazione produceva la stringa $1$23306$2, che PCRE interpretava come $1 + $23 (backreference al gruppo 23, inesistente → stringa vuota, consumando la prima cifra) + 306 (letterale) + $2 (la virgoletta). Il risultato nel file di configurazione era DATABASE_PORT = 306" — valore troncato e virgoletta di chiusura mancante.

Il metodo è stato riscritto usando preg_replace_callback(): il valore di sostituzione viene concatenato direttamente nella closure PHP, senza mai passare per il parser delle backreference PCRE. Questo risolve anche il caso analogo di password contenenti $ o \.

File modificati:

🧪 Test

Tests/Console/Services/Installation/InstallationManagerTest — Copertura regressione valori numerici

✅ Backward Compatibility


[11.6.2] - 2026-06-09 - Correzione Ordine di Discovery dei Comandi Console

Patch che corregge un comportamento anomalo nel CommandDispatcher: i comandi dei moduli venivano scoperti dopo quelli del framework, impedendo ai moduli di estendere o sovrascrivere i comandi nativi. L'ordine è stato invertito — moduli prima (nell'ordine di MODULE_FOLDERS), framework come fallback — allineando il CommandDispatcher alla stessa logica di precedenza già adottata dal web Dispatcher.

🐛 Bug Fixes

Console/HelperClasses/CommandDispatcher — Ordine di discovery dei comandi

Il metodo discoverCommands() scansionava prima la directory dei comandi del framework (SismaFramework/Console/Commands/) e poi quella dei moduli, nell'ordine inverso rispetto al comportamento atteso. Poiché run() si ferma al primo comando compatibile, qualsiasi comando di un modulo con lo stesso nome di un comando del framework veniva silenziosamente ignorato, rendendo impossibile estendere o sovrascrivere i comandi nativi dall'esterno del framework.

L'ordine è stato corretto: i moduli vengono scansionati per primi, rispettando la sequenza dichiarata in MODULE_FOLDERS; il framework viene aggiunto per ultimo come fallback. Questo rispecchia esattamente la logica di precedenza del web Dispatcher e permette ai moduli di estendere i comandi del framework tramite ereditarietà, chiamando parent::execute() dopo aver aggiunto la propria logica.

File modificati:

✅ Backward Compatibility


[11.6.1] - 2026-05-26 - Compatibilità phpDocumentor, Fix SismaLogger e Correzione Documentazione

Patch di manutenzione che allarga il vincolo su psr/log per consentire l'installazione di phpDocumentor come dipendenza di sviluppo, corregge un potenziale TypeError in SismaLogger::interpolate() con messaggi \Stringable, aggiorna la documentazione Markdown (esempi API errati, sezione OAuth mancante) e rigenera la documentazione phpDocumentor allineandola alle classi introdotte in 11.6.0.

🐛 Bug Fixes

Core/HelperClasses/SismaLogger — Gestione \Stringable in interpolate()

Il metodo privato interpolate() dichiarava string $message come tipo del parametro. Poiché LoggerInterface (psr/log 2.x/3.x) consente di passare oggetti \Stringable ai metodi di log, qualsiasi chiamata con un \Stringable avrebbe generato un TypeError prima di raggiungere il metodo. Il tipo è stato aggiornato a \Stringable|string e viene applicato un cast (string) all'inizio del metodo, garantendo la compatibilità con l'intera gamma di messaggi ammessi dall'interfaccia PSR-3.

File modificati:

Console/Services/Installation/InstallationManager — Vincolo psr/log nei nuovi progetti

Il metodo che inietta la dipendenza psr/log nel composer.json dei nuovi progetti impostava il vincolo a ^3.0. Aggiornato a ^2.0 || ^3.0 per allinearlo al vincolo del framework e consentire la coesistenza con phpDocumentor anche nei progetti installati.

File modificati:

🔧 Dipendenze e Tooling

composer.json — Allargamento vincolo psr/log e aggiunta phpDocumentor

Il vincolo "psr/log": "^3.0" impediva l'installazione di phpDocumentor come require-dev, poiché le sue dipendenze indirette richiedono psr/log ^2.0. Il vincolo è stato allargato a ^2.0 || ^3.0: il codice del framework non usa alcuna API specifica di psr/log 3.x (i metodi LoggerInterface sono implementati senza type hint espliciti su $message, compatibili con tutte e tre le major), quindi l'allargamento non introduce alcun rischio regressivo.

Aggiunto inoltre "config": {"platform": {"php": "8.4.99"}} per permettere la risoluzione delle dipendenze su PHP 8.5 (dove phpdocumentor/json-path — dipendenza indiretta — non dichiara ancora supporto esplicito, pur funzionando correttamente). Aggiunto script "phpdoc": "php vendor/bin/phpdoc --config phpdoc.xml" per semplificare la rigenerazione della documentazione API.

File modificati:

🧪 Test

Tests/Console/Services/Installation/InstallationManagerTest — Allineamento asserzioni

Le due asserzioni che verificavano il valore del vincolo psr/log iniettato da InstallationManager sono state aggiornate da '^3.0' a '^2.0 || ^3.0'.

File modificati:

📖 Documentazione

docs/security.md — Correzione esempi API e aggiunta sezione OAuth

La sezione di esempio per l'autenticazione form-based conteneva riferimenti a metodi inesistenti nell'API pubblica (isLogged(), login()) e a un pattern logicamente scorretto (checkAuthenticable() && checkPassword(), dove checkPassword() è già chiamato internamente da checkAuthenticable()). Corretti anche gli accessi alle proprietà di Request (da notazione ad oggetto ->get() a accesso array ['key'], coerente con la definizione della classe) e il nome del metodo getAuthenticable()getAuthenticableInterface().

Aggiunta sezione completa Autenticazione OAuth 2.0 che documenta OAuthAuthentication, OAuthWrapperInterface, il flusso Authorization Code in due fasi e un esempio di implementazione di un wrapper provider.

File modificati:

docs/forms.md — Correzione nome metodo getFilterErrors()

L'esempio del controller utilizzava $form->returnFilterErrors(), metodo inesistente. Corretto in $form->getFilterErrors() (metodo ereditato da SubmittableTrait).

File modificati:

docs/controllers.md — Correzione esempio autowiring Authentication

L'esempio di autowiring utilizzava $auth->isLogged() (metodo inesistente), il namespace errato SismaFramework\Security\Authentication e l'accesso alle proprietà di Request tramite ->get(). Corretti namespace, metodo di verifica sessione e accesso array.

File modificati:

docs/api-reference.md — Correzione firme BaseForm e aggiunta sezioni Security/HTTP

getErrors(): FormFilterErrorCollection era il nome errato del metodo (corretto in getFilterErrors(): FormFilterError); la firma di handleRequest() mancava del parametro Request $request. Aggiunte le sezioni Security Classes (Authentication, OAuthAuthentication, OAuthWrapperInterface, BaseVoter, BasePermission) e HTTP Classes (Response), che erano elencate nell'indice del documento ma mai implementate nel corpo.

File modificati:

docs-phpdoc/ — Rigenerazione completa

Rigenerata da zero tramite composer phpdoc per includere le nuove classi introdotte in 11.6.0 (OAuthAuthentication, OAuthWrapperInterface, BaseAuthentication, SubmittableTrait) ed eliminare il file orfano SismaFramework-Core-AbstractClasses-Submittable.html, rimasto dalla generazione precedente dopo la rimozione del file PHP sorgente.

Correzione annotazioni @deprecated — versione di introduzione e rimozione

Quattro classi/metodi presentavano annotazioni @deprecated incomplete o errate: mancavano la versione in cui la deprecazione era stata introdotta, la versione di rimozione pianificata, oppure il testo era in inglese anziché italiano, creando incoerenza con il resto della codebase.

Orm/ExtendedClasses/DependentModel e Orm/ExtendedClasses/SelfReferencedModel — i tre metodi deprecati (countEntityCollectionByEntity, getEntityCollectionByEntity, deleteEntityCollectionByEntity) riportavano dalla versione 11.0.0, ma la deprecazione era stata introdotta in v10.1.0 (commit 9c9f5ed4, 2025-11-21). Corretto in dalla versione 10.1.0; aggiunta la versione di rimozione pianificata 12.0.0.

Security/ExtendedClasses/LogException e Security/ExtendedClasses/NoLogException — le annotazioni erano in inglese e prive di numeri di versione. La deprecazione è stata introdotta in v11.0.0 (commit 87843e03, 2025-12-18). Aggiunta versione di introduzione 11.0.0, versione di rimozione 12.0.0; testo armonizzato in italiano coerentemente con gli altri messaggi di deprecazione del framework.

File modificati:

✅ Backward Compatibility


[11.6.0] - 2026-05-04 - Rifattorizzazione Gerarchia di Autenticazione, Introduzione SubmittableTrait e Supporto OAuth

Rifattorizzazione interna del sistema di autenticazione: la classe astratta Submittable è stata convertita in un trait, e il comportamento comune a tutte le classi di autenticazione è stato estratto nella nuova classe astratta BaseAuthentication. Il refactoring ha abilitato l'implementazione di OAuthAuthentication, che supporta il flusso Authorization Code OAuth 2.0 senza SubmittableTrait poiché in OAuth non esiste un form da sottomettere né errori di validazione da riportare al template.

♻️ Refactoring

Core/Traits/SubmittableTrait — Conversione da classe astratta a trait

Submittable era una classe astratta @internal usata come base sia da Authentication che da BaseForm, pur non rappresentando un tipo condiviso tra le due gerarchie, bensì un comportamento ortogonale (rilevamento form submission). È stata convertita in un trait e spostata in Core/Traits/.

Il trait espone:

File modificati:

Security/BaseClasses/BaseAuthentication — Nuova classe astratta base per l'autenticazione

Estratta da Authentication la logica comune a qualsiasi flusso di autenticazione (form-based, OAuth, ecc.). La nuova classe astratta BaseAuthentication, marcata @internal, centralizza:

SubmittableTrait non è incluso in BaseAuthentication perché non tutti i flussi di autenticazione hanno un form: Authentication (form-based) lo usa, OAuthAuthentication no.

File modificati:

Security/HttpClasses/Authentication — Adeguamento alla nuova gerarchia

Authentication passa da extends Submittable a extends BaseAuthentication con use SubmittableTrait. Le property $filter, $session, $authenticableInterface e il metodo getAuthenticableInterface() sono stati spostati in BaseAuthentication. Il costruttore chiama parent::__construct() e $this->initSubmittable().

File modificati:

Core/BaseClasses/BaseForm — Adeguamento al SubmittableTrait

BaseForm passa da extends Submittable a use SubmittableTrait, dichiarando protected Request $request direttamente nella classe. Il costruttore sostituisce parent::__construct() con $this->initSubmittable().

File modificati:

✨ Nuove Funzionalità

Security/HttpClasses/OAuthAuthentication — Autenticazione OAuth 2.0 Authorization Code Flow

Nuova classe OAuthAuthentication extends BaseAuthentication che implementa il flusso Authorization Code OAuth 2.0. Non usa SubmittableTrait perché in OAuth non esiste un form da sottomettere: gli errori arrivano come parametri URL dal provider e vengono gestiti tramite valori di ritorno ed eccezioni, non tramite FormFilterError.

Il flusso si articola in due fasi:

Fase 1 — Redirect al provider:

Fase 2 — Callback dal provider:

La protezione CSRF del callback segue lo stesso pattern difensivo di Authentication::checkCsrfToken(): verifica sequenziale con early return.

File modificati:

Security/Interfaces/Wrappers/OAuthWrapperInterface — Contratto per i provider OAuth

Nuova interfaccia che astrae la comunicazione con il provider OAuth. Ogni provider (Google, GitHub, ecc.) implementa:

File modificati:

🧪 Test

Tests/Security/HttpClasses/OAuthAuthenticationTest — Copertura completa del flusso OAuth

Sette test che coprono tutti i percorsi di checkCallback() e getAuthorizationUrl():

File modificati:

✅ Backward Compatibility


[11.5.2] - 2026-04-04 - Rifattorizzazione Template Controller nello Scaffolding

Piccola rifattorizzazione del template del controller generato dal comando di scaffolding, per semplificare eventuali personalizzazioni post-generazione.

♻️ Refactoring

Console/Services/Scaffolding/Templates/Controller.tpl — Estrazione variabile entità prima del salvataggio

Nelle azioni create e update, la chiamata a resolveEntity() era concatenata direttamente come argomento di $this->dataMapper->save() su un'unica riga. L'entità risolta viene ora assegnata a una variabile dedicata prima di essere passata al DataMapper.

  ${{entityShortNameLower}} = ${{entityShortNameLower}}Form->resolveEntity();
  $this->dataMapper->save(${{entityShortNameLower}});

Questo rende il codice generato più leggibile e facilita eventuali personalizzazioni (es. manipolare l'entità tra resolveEntity() e save()), senza alcuna modifica al comportamento a runtime.

Rimossi inoltre i trailing whitespace sulle righe vuote tra i metodi della classe.

File modificati:

✅ Backward Compatibility


[11.5.1] - 2026-04-01 - Correzione Template Controller nello Scaffolding

Questa patch corregge due bug nel template del controller generato dal comando di scaffolding.

🐛 Bug Fixes

Console/Services/Scaffolding/Templates/Controller.tpl — Namespace modello errato e metodo form scorretto

Bug 1 — Namespace use del modello con segmento Models duplicato

Il namespace nell'istruzione use includeva un segmento \Models\ ridondante: poiché {{modelNamespace}} contiene già il segmento Models, il risultato era una duplicazione (es. …\Models\Models\{{entityShortName}}Model), producendo un'istruzione non valida nel controller generato.

Bug 2 — Uso di getEntity() al posto di resolveEntity() nelle azioni create ed edit

Il salvataggio dell'entità nelle azioni create ed edit chiamava getEntity(), che non risolve correttamente le relazioni del form. Il metodo corretto è resolveEntity().

File modificati:

✅ Backward Compatibility


[11.5.0] - 2026-03-15 - Supporto Cross-Platform per il Comando sisma

Questa minor aggiunge il supporto nativo al comando sisma su Windows e semplifica l'avvio su Linux/macOS tramite shebang.

✨ Nuove Funzionalità

Console/sisma — Aggiunto shebang #!/usr/bin/env php

Lo script sisma può ora essere invocato direttamente da terminale su Linux e macOS (es. sisma fixtures) senza anteporre php, grazie alla riga shebang. PHP ignora la riga #! quando il file viene eseguito tramite php sisma, garantendo piena retrocompatibilità.

File modificati:

Console/sisma.bat — Nuovo wrapper per Windows

Aggiunto file sisma.bat nella stessa directory di sisma, che consente di invocare il comando come sisma fixtures anche su Windows nativo (senza Docker). Windows riconosce automaticamente l'estensione .bat quando il nome del comando è nel PATH.

File aggiunti:

✅ Backward Compatibility


[11.4.1] - 2026-03-11 - Consolidamento Bootstrap e Estrazione enableErrorDisplay() in ErrorHandler

Questa patch consolida la gestione del bootstrap nei due entry point del framework. Il require_once dell'autoload di Composer viene spostato direttamente nello skeleton di index.php, semplificando la procedura d'installazione. La logica di abilitazione degli errori viene estratta in un metodo statico di ErrorHandler, eliminando la dipendenza da LoggerInterface nel contesto di bootstrap della console.

♻️ Refactoring

ErrorHandler — Estrazione di enableErrorDisplay() come metodo statico

La versione 11.4.0 aveva introdotto showErrorInDevelopmentEnvironment() come metodo d'istanza, usato anche nello script sisma. Questo richiedeva l'istanziazione di ErrorHandler e quindi la dipendenza da Psr\Log\LoggerInterface (via vendor autoload) già in fase di bootstrap della console, prima ancora di qualsiasi comando. Il blocco ini_set è stato estratto nel nuovo metodo statico enableErrorDisplay(), senza dipendenze esterne, riutilizzabile sia da sisma che internamente da showErrorInDevelopmentEnvironment().

Modifica:

File modificati:

Public/index.phpvendor/autoload.php incluso nello skeleton

Il require_once dell'autoload di Composer veniva iniettato dinamicamente da InstallationManager::copyPublicFolder() tramite manipolazione di stringa sul file copiato. Poiché il percorso relativo dirname(__DIR__) . '/vendor/autoload.php' è invariante sia nello skeleton (SismaFramework/vendor/) sia nel progetto installato (projectRoot/vendor/), la riga è ora inclusa direttamente nel file sorgente. L'ordine di caricamento è: autoload SismaFramework prima, autoload vendor dopo.

File modificati:

✅ Backward Compatibility


[11.4.0] - 2026-03-09 - Auto-discovery dei Comandi Console tramite Factory Pattern

Questa minor introduce l'auto-discovery automatico dei comandi console tramite il pattern factory nel CommandDispatcher, allineando l'architettura della console a quella del Dispatcher HTTP. I comandi non devono più essere registrati manualmente nello script sisma: vengono scoperti automaticamente sia nel framework che in tutti i moduli configurati.

✨ Nuove Funzionalità

CommandDispatcher — Auto-discovery dei comandi via factory

Il CommandDispatcher registrava i comandi esclusivamente tramite chiamate esplicite ad addCommandStrategy() nello script di avvio. Questo approccio richiedeva la modifica manuale del file sisma ad ogni aggiunta di un nuovo comando, sia nel framework che nei moduli applicativi.

Ora il costruttore invoca internamente discoverCommands(), che scansiona via reflection le directory Console/Commands/ del framework e di tutti i moduli presenti in Config::$moduleFolders, istanziando automaticamente ogni classe concreta che estende BaseCommand tramite la nuova classe CommandFactory.

Modifica:

File modificati:

Console/sisma — Rimozione registrazione manuale dei comandi

Lo script di avvio della console registrava esplicitamente tutti i comandi del framework (FixturesCommand, InstallationCommand, ScaffoldCommand, UpgradeCommand) tramite addCommandStrategy(). Con l'auto-discovery queste chiamate sono ridondanti e sono state rimosse.

Sostituito inoltre il blocco error_reporting / ini_set con l'utilizzo di ErrorHandler::showErrorInDevelopmentEnvironment(), in linea con il resto del framework.

File modificati:

✅ Backward Compatibility


[11.3.6] - 2026-03-08 - Transazione Atomica nell'Esecuzione delle Fixtures e Fix Rollback

Questa patch introduce l'esecuzione atomica delle fixtures tramite una transazione globale nel FixturesManager, e corregge il comportamento del TransactionManager::rollback() che non verificava lo stato attivo della transazione prima di eseguire il rollback sul database.

🐛 Bug Fixes

TransactionManager::rollback() — Guardia su transazione attiva

Il metodo rollback() eseguiva $this->adapter->rollbackTransaction() incondizionatamente, senza verificare se una transazione fosse effettivamente aperta. Questo poteva causare un errore del driver database in caso di chiamata su connessione senza transazione attiva.

Modifica:

File modificati:

✨ Miglioramenti

FixturesManager::run() — Esecuzione atomica tramite transazione globale

L'esecuzione delle fixtures avveniva senza una transazione globale: ogni save() apriva e chiudeva la propria transazione autonomamente. In caso di errore a metà esecuzione, i record già inseriti dai fixture precedenti rimanevano nel database.

Ora run() apre una transazione prima di eseguire i fixture e la committa solo al termine di tutti. Se un save() fallisce internamente, esegue il rollback dell'intera transazione e rilancia l'eccezione, che propaga naturalmente al sisma script.

File modificati:

Console/sisma — Path con DIRECTORY_SEPARATOR

Il file di avvio della console usava / hardcoded per costruire i path di configurazione e autoload, causando potenziali problemi su sistemi Windows.

File modificati:

🧪 Test

ScaffoldingManagerTest::testDoubleExecution — Path con DIRECTORY_SEPARATOR

Il messaggio di eccezione atteso nel test usava \ hardcoded per il path, causando il fallimento del test su sistemi Linux/macOS dove il separatore è /.

File modificati:

✅ Backward Compatibility


[11.3.5] - 2026-03-04 - Ripristino Compatibilità PHP in ModuleManager

Questa patch ripristina il codice precedente nel metodo setApplicationModuleByClassName() della classe ModuleManager, rimuovendo l'uso di array_first() introdotto involontariamente nella versione 11.3.4. La funzione array_first() è disponibile solo a partire da PHP 8.5, incompatibile con il requisito minimo del framework (PHP 8.3).

🐛 Bug Fixes

Ripristino accesso diretto all'array in ModuleManager::setApplicationModuleByClassName()

Il commit della versione 11.3.4 aveva sostituito $classNameParts[0] con array_first($classNameParts), funzione introdotta in PHP 8.5 e non disponibile in PHP 8.3 e 8.4.

Ripristino:

File modificati:

✅ Backward Compatibility


[11.3.4] - 2026-03-03 - Impostazione Modulo nella Classe ErrorHandler

Questa patch release corregge un bug per cui la classe ErrorHandler non impostava il modulo applicativo prima di invocare i controller di errore, causando un fallimento nella risoluzione delle view di errore.

🐛 Bug Fixes

Impostazione del modulo nei metodi di gestione errori

I metodi pubblici di ErrorHandler chiamavano i controller di errore senza prima impostare il modulo tramite ModuleManager, a differenza di quanto avviene nel Dispatcher. Il sistema di rendering non riusciva quindi a individuare il percorso corretto delle view.

Modifiche applicate:

File modificati:

🧪 Test

Correzione BackupManagerTest con estensione ZIP non disponibile

Il tearDown() accedeva alla proprietà tipizzata $testDir anche quando setUp() aveva chiamato markTestSkipped() prima di inizializzarla (assenza dell'estensione ZIP), causando un errore Typed property must not be accessed before initialization.

Fix: l'assegnazione di $testDir è stata spostata prima del controllo sull'estensione, garantendo che la proprietà sia sempre inizializzata prima che tearDown() venga eseguito.

File modificati:

✅ Backward Compatibility


[11.3.3] - 2026-02-22 - Ripristino Proprietà project nella Classe Config

Questa patch release corregge un errore introdotto nella versione 11.3.2, dove la proprietà $project era stata erroneamente rimossa dalla classe Config nonostante venisse ancora utilizzata dal FrameworkController.

🐛 Bug Fixes

Ripristino di Config::$project

La proprietà $project era stata inclusa per errore nell'elenco delle proprietà "orfane" rimosse nella versione 11.3.2. In realtà viene letta a runtime in FrameworkController::throwableError() e FrameworkController::nonThrowableError() per popolare la variabile di template $vars['project'] nella pagina di errore visibile.

File modificati:

✅ Backward Compatibility


[11.3.2] - 2026-02-21 - Spostamento Fixtures nella Console

Questa patch release rifattorizza il sistema di esecuzione delle fixtures, spostandolo dal contesto HTTP (Dispatcher) al contesto CLI (Console). Il comportamento delle fixtures resta invariato: cambiano solo il punto di invocazione e la collocazione del codice. Include inoltre la documentazione del sistema di Upgrade introdotto nella versione 11.3.0.

🔧 Refactoring

Migrazione delle Fixtures dal Dispatcher alla Console

Le fixtures erano una funzionalità nata quando il framework non disponeva di una console CLI. Venivano eseguite tramite un endpoint HTTP (/fixtures), integrato nel Dispatcher e nel RouteResolver. Con l'introduzione della console, questa collocazione risultava architetturalmente inadeguata.

Nuovo comando CLI:

php SismaFramework/Console/sisma fixtures

File creati:

File modificati:

File eliminati:

Modifiche al FixturesManager:

Pulizia proprietà orfane nella classe Config

Rimosse 10 proprietà dalla classe Config che non venivano mai lette a runtime tramite $config->proprietà. Queste proprietà esistevano come mapping delle corrispondenti costanti in config.php, ma nessun codice PHP le accedeva — le costanti servono esclusivamente come building block per la composizione di altre costanti e restano invariate.

Proprietà rimosse: $adapters, $assets, $cache, $core, $defaultController, $logs, $project, $resources, $thisDirectory, $directoryUp

✅ Test

📖 Documentazione

✅ Backward Compatibility


[11.3.1] - 2026-02-11 - Correzione Percorsi Cross-Platform nell'Autoloader

Questa patch release corregge un bug nell'Autoloader che impediva il caricamento delle classi mappate tramite AUTOLOAD_NAMESPACE_MAPPER e AUTOLOAD_CLASS_MAPPER su sistemi Linux/macOS.

🐛 Bug Fixes

Correzione Conversione Separatori di Directory nei Mapper dell'Autoloader

Corretti i metodi mapNamespace() e mapClass() in Autoloader.php per convertire correttamente i backslash nei percorsi provenienti dalle costanti di configurazione:

Scenario del bug:

  1. La configurazione AUTOLOAD_NAMESPACE_MAPPER contiene percorsi con backslash (es. "plugins\PHPMailer\src")
  2. Su Windows, i backslash funzionano come separatori di directory, mascherando il problema
  3. Su Linux/macOS (es. dentro un container Docker), DIRECTORY_SEPARATOR è /
  4. Il percorso risultante conteneva backslash letterali: /var/www/html/plugins\PHPMailer\src/PHPMailer.php
  5. file_exists() falliva perché il percorso non era valido su Linux
Dopo la correzione:

✅ Backward Compatibility

📊 Impatto


[11.3.0] - 2026-02-08 - Sistema di Upgrade Automatico e Miglioramenti ORM Fulltext

Questa release introduce un sistema completo di upgrade automatico che consente di migrare moduli tra versioni major del framework applicando automaticamente le trasformazioni necessarie per i breaking changes. Inoltre, viene aggiunto il parametro TextSearchMode ai metodi di ricerca fulltext dell'ORM, consentendo un controllo esplicito sulla modalità di ricerca testuale.

✨ Nuove Funzionalità

  {
    "name": "ModuleName",
    "version": "1.0.0",
    "framework_version": "11.0.0",
    "description": "Module description",
    "authors": ["Author Name"],
    "requires": {
      "sismaframework": ">=11.0.0"
    }
  }

| Trasformazione | Confidence | Note | |----------------|------------|------| | Static→Instance | 70-75% | Alta per index.php, warning per altri file | | ReturnType void→bool | 80-85% | Rilevamento automatico indentazione | | Response constructor | 65-70% | Warning per pattern complessi | | Method renaming | 90% | Alta affidabilità |

🛡️ Sicurezza e Affidabilità

📊 Report e Trasparenza

🔧 Exceptions

📋 Esempi di Utilizzo

# Preview upgrade (raccomandato come primo step)
php Console/sisma upgrade Blog --to=11.0.0 --dry-run

# Applicazione upgrade dopo review
php Console/sisma upgrade Blog --to=11.0.0

# Upgrade da versione specifica
php Console/sisma upgrade Blog --from=10.1.7 --to=11.0.0

# Salta file critici per review manuale
php Console/sisma upgrade Blog --to=11.0.0 --skip-critical

# Output minimale
php Console/sisma upgrade Blog --to=11.0.0 --quiet

🔄 Estensibilità

Il sistema è progettato per essere facilmente estensibile:

  1. Nuova Major Version: Creare Upgrade11to12Strategy.php implementando UpgradeStrategyInterface
  2. Nuove Trasformazioni: Creare transformer implementando TransformerInterface
  3. Custom Strategies: Sistema a plugin completamente estensibile

🔧 Miglioramenti ORM

Aggiunta parametro TextSearchMode alla ricerca fulltext

Aggiunto il parametro TextSearchMode ai metodi di ricerca fulltext per consentire un controllo esplicito sulla modalità di ricerca testuale:

Motivazione:

⚠️ Limitazioni

🛠️ Mitigazioni


[11.2.0] - 2026-01-30 - Aggiornamento Requisiti PHP e PHPUnit

Questa minor release aggiorna i requisiti minimi del framework a PHP 8.3 e PHPUnit 12, allineandosi con le versioni attivamente supportate e sfruttando le feature moderne del linguaggio già presenti nel codebase. Inoltre, il processo di installazione ora crea automaticamente la struttura del modulo applicativo.

✨ Nuove Funzionalità

Creazione Automatica Struttura Modulo durante Installazione

Il comando install ora crea automaticamente la struttura completa del modulo applicativo:

Struttura creata automaticamente:

MyProject/
├── Application/
│   ├── Controllers/
│   ├── Entities/
│   ├── Enumerations/
│   ├── Forms/
│   ├── Models/
│   └── Views/
├── Config/
│   └── configFramework.php
├── Public/
│   └── index.php
├── Cache/
├── Logs/
├── filesystemMedia/
├── .htaccess
└── composer.json

Vantaggi:

🔧 Aggiornamenti Requisiti

PHP 8.3 come Requisito Minimo

Motivazione:

PHPUnit 12 come Requisito per i Test

Motivazione:

🔍 Miglioramenti Qualità Codice

Completamento Attributi #[\Override]

Aggiunti gli attributi #[\Override] mancanti ai metodi che sovrascrivono metodi di classi parent o implementano metodi di interfacce:

Vantaggi:

🧪 Miglioramenti Test Suite

Conformità PHPUnit 12

Aggiornati i test per conformarsi alle best practice di PHPUnit 12:

Riorganizzazione Configurazione PHPUnit

Vantaggi:

📖 Utilizzo

# Esecuzione test (PHPUnit trova automaticamente phpunit.xml)
./vendor/bin/phpunit

# Esecuzione test senza code coverage
./vendor/bin/phpunit --no-coverage

# Esecuzione test con coverage (richiede Xdebug)
XDEBUG_MODE=coverage ./vendor/bin/phpunit

✅ Backward Compatibility

📚 Aggiornamento Documentazione e Materiali Promozionali

Tutti i riferimenti alla versione PHP sono stati aggiornati da 8.1 a 8.3 nei seguenti file:

📊 File Modificati

FileTipoDescrizione
composer.jsonModificatoRequisiti PHP ≥8.3, PHPUnit ^12.0
.gitignoreModificatoAggiunto composer.lock
Console/Services/Installation/InstallationManager.phpModificatoAggiunta chiamata a initializeModule()
Console/Commands/InstallationCommand.phpModificatoAggiornato output installazione
Core/CustomTypes/FormFilterErrorCollection.phpModificatoAggiunto #[\Override]
Core/HelperClasses/SismaLogger.phpModificatoAggiunto #[\Override] ai metodi PSR-3
Core/HelperClasses/SismaLogReader.phpModificatoAggiunto #[\Override]
Orm/CustomTypes/SismaDate.phpModificatoAggiunto #[\Override]
Orm/CustomTypes/SismaDateTime.phpModificatoAggiunto #[\Override]
Orm/CustomTypes/SismaTime.phpModificatoAggiunto #[\Override]
Orm/BaseClasses/BaseResultSet.phpModificatoAggiunto #[\Override] ai metodi Iterator
Orm/ExtendedClasses/SelfReferencedModel.phpModificatoAggiunto #[\Override]
phpunit.xmlNuovo (rinominato)Configurazione PHPUnit spostata nella root
Tests/configuration.xmlRimossoSostituito da phpunit.xml
Tests/Console/Services/Installation/InstallationManagerTest.phpModificatoAggiunto test per verifica creazione modulo
Tests/Core/BaseClasses/BaseControllerTest.phpModificatocreateMockcreateStub
Tests/Core/HelperClasses/ErrorHandlerTest.phpModificatocreateMockcreateStub
Tests/Core/Services/RenderServiceTest.phpModificatocreateMockcreateStub
Tests/Core/Services/RouterServiceTest.phpModificatocreateMockcreateStub
TestsApplication/Controllers/SampleController.phpModificatoAggiunto #[\Override]

[11.1.0] - 2026-01-21 - Input Interattivo per Configurazione Database

Questa minor release aggiunge la possibilità di configurare i parametri del database in modo interattivo durante l'installazione del framework, migliorando l'esperienza utente senza compromettere la retrocompatibilità.

✨ Nuove Funzionalità

Input Interattivo da Console

Aggiunto nuovo trait InteractiveInputTrait per gestire l'input utente dalla console:

Configurazione Database Interattiva

Migliorato InstallationCommand con richiesta interattiva dei parametri database:

📖 Utilizzo

# Installazione con richiesta interattiva database
php SismaFramework/Console/sisma install MyProject

# Installazione senza configurazione database
php SismaFramework/Console/sisma install MyProject --skip-db

# Installazione con parametri da command line (comportamento precedente)
php SismaFramework/Console/sisma install MyProject --db-host=localhost --db-name=mydb --db-user=root

Esempio di output interattivo:

Installing SismaFramework project: MyProject

Database Configuration (optional)
Press Enter to skip each field or use defaults.

Do you want to configure database settings? [y/N]: y
Database Host [127.0.0.1]: localhost
Database Port [3306]: 3306
Database Name []: myproject_db
Database Username []: root
Database Password: ********

Creating project structure...

✅ Backward Compatibility

🔧 Dettagli Tecnici

  • Il trait InteractiveInputTrait può essere riutilizzato da altri comandi o moduli
  • Su Windows, askSecret() non nasconde l'input (limitazione del sistema)
  • Su Linux/macOS, askSecret() utilizza stty -echo per nascondere l'input

📊 File Modificati

FileTipoDescrizione
Console/Traits/InteractiveInputTrait.phpNuovoTrait per input interattivo
Console/Commands/InstallationCommand.phpModificatoAggiunta configurazione interattiva DB

[11.0.5] - 2026-01-21 - Correzione Sostituzione Costanti File Configurazione

Questa patch release corregge un bug nel processo di installazione che impediva la corretta sostituzione delle costanti nel file di configurazione quando queste utilizzavano apici doppi invece di apici singoli.

🐛 Bug Fixes

Correzione Regex Sostituzione Costanti

Corrette le espressioni regolari in InstallationManager.php per supportare sia apici singoli (') che apici doppi (") nella sostituzione delle costanti:

Scenario del bug:

  1. Il file Config/config.php del framework utilizza apici doppi per le stringhe (es. const PROJECT = "SismaFramework")
  2. Le regex in InstallationManager cercavano solo apici singoli
  3. Durante l'installazione (php SismaFramework/Console/sisma install MyProject), le costanti non venivano sostituite
  4. Il file configFramework.php risultante manteneva i valori originali invece di quelli specificati dall'utente
Dopo la correzione:

  • Le costanti vengono sostituite correttamente indipendentemente dal tipo di apice usato
  • Il processo di installazione funziona sia con file di configurazione che usano apici singoli che doppi
  • Maggiore robustezza e compatibilità del processo di installazione

🧪 Testing

Aggiornamento Test InstallationManager

Aggiornati i test per essere agnostici rispetto al tipo di apice utilizzato:

Esempio di verifica aggiornata:

// ❌ Prima (verificava solo apici singoli):
$this->assertStringContainsString("const PROJECT = 'MyTestProject'", $content);

// ✅ Dopo (verifica entrambi i tipi di apici):
$this->assertMatchesRegularExpression("/const PROJECT = ['\"]MyTestProject['\"]/", $content);

✅ Backward Compatibility

📊 Impatto


[11.0.4] - 2026-01-17 - Correzione Percorso File di Log

Questa patch release corregge un bug nel file di configurazione del framework dove la costante LOG_PATH puntava a un percorso errato.

🐛 Bug Fixes

Correzione Costante LOG_PATH

Corretto il percorso del file di log nella configurazione predefinita del framework:

Scenario del bug:

  1. La costante LOG_PATH era definita come DIRECTORY_SEPARATOR . 'log.txt'
  2. Questo puntava erroneamente alla root del filesystem (/log.txt su Linux, \log.txt su Windows)
  3. Nella configurazione standard, questa costante non viene modificata durante l'installazione
  4. Il file di log non veniva scritto nella posizione corretta (Sample/Logs/log.txt)
Dopo la correzione:

  • LOG_PATH utilizza correttamente LOG_DIRECTORY_PATH come base del percorso
  • Il file di log viene creato nella directory corretta: {ROOT}/Sample/Logs/log.txt
  • Il sistema di logging funziona correttamente senza necessità di configurazione manuale

✅ Backward Compatibility

📊 Impatto


[11.0.3] - 2026-01-08 - Correzione Installazione File .htaccess

Questa patch release corregge un bug nel processo di installazione che non copiava il file .htaccess necessario per il reindirizzamento verso la directory Public.

🐛 Bug Fixes

Copia File .htaccess Durante Installazione

Aggiunta la copia del file .htaccess durante il processo di installazione automatica:

Scenario del bug:

  1. Utente esegue: php SismaFramework/Console/sisma install MyProject
  2. Il file .htaccess non veniva copiato nella root del progetto
  3. Il web server non riusciva a reindirizzare correttamente le richieste verso Public/index.php
  4. L'applicazione non funzionava correttamente senza configurazione manuale del virtual host
Dopo la correzione:

  • Il file .htaccess viene copiato automaticamente nella root del progetto
  • Il file contiene le regole di reindirizzamento verso Public/ già configurate
  • L'applicazione funziona immediatamente senza configurazione aggiuntiva del web server

Contenuto del file .htaccess:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{QUERY_STRING} fbclid= [NC]
RewriteRule ^(.*)$ /$1? [R=301,L]
RewriteCond %{THE_REQUEST} \ /(.+/)?index\.php\/(.*)$ [NC]
RewriteRule ^(.+/)?index\.php\/(.*)$ /$1$2 [R=301,L]
RewriteCond %{THE_REQUEST} \ /(.+/)?index\.php(.*)$ [NC]
RewriteRule ^(.+/)?index\.php(.*)$ /$1$2 [R=301,L]
RewriteCond %{REQUEST_URI} !^/Public/
RewriteRule ^(.*)$ Public/ [L]
</IfModule>

✅ Backward Compatibility

📊 Impatto


[11.0.1] - 2026-01-06 - Correzioni Processo di Installazione

Questa patch release corregge tre bug critici nel processo di installazione automatica del framework che causavano errori nella generazione del file composer.json e nel riferimento al file di configurazione.

🐛 Bug Fixes

Correzione Formato Nome Composer.json

Corretto il formato del nome del progetto nel file composer.json generato durante l'installazione:

Scenario del bug:

  1. Utente esegue: php SismaFramework/Console/sisma install MyProject
  2. Il file composer.json veniva creato con "name": "myproject" invece di "name": "vendor/myproject"
  3. Questo causava errori durante composer install o composer update perché il formato non era valido
Dopo la correzione:

  • Il file composer.json ha il formato nome corretto: "vendor/myproject"
  • Il comando composer install funziona senza errori

Correzione Percorso configFramework.php in index.php

Corretto il percorso del file di configurazione nel file Public/index.php generato durante l'installazione:

Scenario del bug:

  1. Durante l'installazione, il file Config/configFramework.php viene creato nella root del progetto
  2. Il file Public/index.php veniva modificato per cercare il config in SismaFramework/Config/configFramework.php
  3. L'applicazione non trovava il file di configurazione causando errori fatali
Dopo la correzione:

  • Il file Public/index.php include correttamente dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Config' . DIRECTORY_SEPARATOR . 'configFramework.php'
  • Il percorso Autoload viene correttamente modificato in SismaFramework/Autoload
  • L'applicazione trova il file di configurazione e si avvia correttamente

Correzione ROOT_PATH in configFramework.php

Corretto il numero di livelli di risalita della costante ROOT_PATH nel file di configurazione installato:

Scenario del bug:

  1. Il file originale SismaFramework/Config/config.php si trova in root/SismaFramework/Config/config.php
  2. La ROOT_PATH sale di due livelli: SismaFramework/ConfigSismaFrameworkroot (corretto per il framework)
  3. Il file installato Config/configFramework.php si trova in root/Config/configFramework.php
  4. Con due livelli di risalita: Configrootparent (errato)
  5. Con un livello di risalita: Configroot (corretto)
Dopo la correzione:

  • La costante ROOT_PATH punta correttamente alla root del progetto
  • Tutti i percorsi derivati (cache, log, etc.) funzionano correttamente

🧪 Testing

Aggiornamento Test Suite

Aggiornati i test per riflettere le correzioni apportate:

- ❌ Prima: Verificava presenza di 'SismaFramework' . DIRECTORY_SEPARATOR . 'Config'

- ✅ Dopo: Verifica presenza di 'Config' . DIRECTORY_SEPARATOR . 'configFramework.php'

- ❌ Prima: Verificava nome come 'mytestproject'

- ✅ Dopo: Verifica nome come 'vendor/mytestproject'

✅ Backward Compatibility

1. Aggiornare manualmente composer.json per aggiungere il prefisso vendor/

2. Verificare che Public/index.php punti a Config/configFramework.php e non a SismaFramework/Config/configFramework.php 3. Verificare che ROOT_PATH in Config/configFramework.php salga di un solo livello

📊 Impatto


[11.0.0] - 2026-01-02 - Rifattorizzazione Architetturale e Semplificazione API

Questa major release introduce miglioramenti architetturali significativi: rifattorizzazione completa di BaseForm con principi SOLID, semplificazione API Response attraverso rimozione del metodo pubblico setResponseType(), e implementazione completa dello standard PSR-3 per il logging con supporto per logger di terze parti.

La release introduce breaking changes: il metodo astratto customFilter() di BaseForm ora ritorna bool invece di void, il metodo pubblico setResponseType() di Response è stato rimosso in favore dell'immutabilità tramite constructor injection, e le classi ErrorHandler e Debugger sono state trasformate da statiche a di istanza per migliorare testabilità e dependency injection.

🏗️ Architettura

Vantaggi della rifattorizzazione:

  • Ogni classe ha una singola, chiara responsabilità (SRP)
    • Codice più testabile con dipendenze iniettabili
    • Migliore leggibilità e manutenibilità
    • Facilita l'estensione con validatori o filtri custom
    • Riduce la complessità della classe BaseForm da oltre 400 linee a circa 200
  public function __construct(
      ?BaseEntity $baseEntity = null,
      DataMapper $dataMapper = new DataMapper(),
      FilterManager $filterManager = new FilterManager(),
      ?FormValidator $formValidator = null,
      EntityResolver $entityResolver = new EntityResolver()
  )

Questo permette di iniettare implementazioni custom per testing o estensioni.

Vantaggi dell'implementazione PSR-3:

Esempio di utilizzo con logger custom:

  use Monolog\Logger;
  use Monolog\Handler\StreamHandler;
  
  // Logger di terze parti (Monolog)
  $monolog = new Logger('app');
  $monolog->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
  
  // Injection in ErrorHandler
  $errorHandler = new ErrorHandler(logger: $monolog);
  $errorHandler->registerNonThrowableErrorHandler();
    // Prima (10.x):
    ErrorHandler::disableErrorDisplay();
    ErrorHandler::handleNonThrowableError();
    Debugger::startExecutionTimeCalculation();
    
    // Dopo (11.0.0):
    $errorHandler = new ErrorHandler();
    $errorHandler->disableErrorDisplay();
    $errorHandler->registerNonThrowableErrorHandler();
    $debugger = new Debugger();
    $debugger->startExecutionTimeCalculation();
    $dispatcher = new Dispatcher(debugger: $debugger);

Motivazione della trasformazione:

Vantaggi del Pattern Facade + Singleton:

Utilizzo nei Controller:

  class ProductController extends BaseController
  {
      public function index(): Response
      {
          // Sintassi di istanza (nuovo, preferito):
          return $this->render->generateView('product/index', $this->vars);
          
          // Sintassi statica (legacy, ancora supportato):
          return Render::generateView('product/index', $this->vars);
      }
      
      public function create(): Response
      {
          // Sintassi di istanza per router:
          return $this->router->redirect('product/list');
          
          // Sintassi statica (legacy):
          return Router::redirect('product/list');
      }
  }
  protected RenderService $router;
  protected RenderService $render;
  
  public function __construct(DataMapper $dataMapper = new DataMapper(), Debugger $debugger = new Debugger())
  {
      $this->dataMapper = $dataMapper;
      $this->debugger = $debugger;
      $this->router = RouterService::getInstance();
      $this->render = RenderService::getInstance();
      // ...
  }

Questo permette di utilizzare $this->render e $this->router come istanze in tutti i controller che estendono BaseController.

💥 Breaking Changes

Prima (10.x):

  $response = new Response();
  $response->setResponseType(ResponseType::httpNotFound);

Dopo (11.0.0):

  $response = new Response(ResponseType::httpNotFound);

Motivazione:

  • Promuove immutabilità: un oggetto Response dovrebbe nascere con un tipo e mantenerlo
    • Proprietà $responseType ora readonly (PHP 8.1+) per garantire immutabilità a livello di linguaggio
    • Semplifica API: constructor injection è più pulito e type-safe
    • Elimina metodi non utilizzati: nessun codice nel framework usava setResponseType() dopo creazione oggetto
    • Migliora testabilità: stato dell'oggetto più prevedibile

Impatto: Il metodo setResponseType() non è più disponibile. Utilizzare il costruttore per impostare il response type.

Azione richiesta:

  • Sostituire chiamate a setResponseType() passando il ResponseType al costruttore
    • Se necessario modificare il response type, creare una nuova istanza di Response

Prima (10.x):

  abstract protected function customFilter(): void;

Dopo (11.0.0):

  abstract protected function customFilter(): bool;

Motivazione: Il nuovo tipo di ritorno bool permette al metodo customFilter() di contribuire al risultato finale di validazione del form. Ritornando true se la validazione custom ha successo o false in caso di errori, si ottiene un'API più coerente e un flusso di validazione più chiaro.

Impatto: Tutte le classi che estendono BaseForm devono essere aggiornate per ritornare un valore booleano dal metodo customFilter().

Azione richiesta:

  • Aggiungere return true; alla fine del metodo customFilter() se non ci sono errori di validazione custom
    • Ritornare false quando la validazione custom fallisce
    • Esempio:
    // Prima (10.x):
    protected function customFilter(): void
    {
        if ($this->entity->startDate > $this->entity->endDate) {
            $this->formFilterError->startDateError = true;
        }
    }

    // Dopo (11.0.0):
    protected function customFilter(): bool
    {
        if ($this->entity->startDate > $this->entity->endDate) {
            $this->formFilterError->startDateError = true;
            return false;
        }
        return true;
    }

Prima (10.x):

  ErrorHandler::disableErrorDisplay();
  ErrorHandler::handleNonThrowableError();
  Debugger::startExecutionTimeCalculation();

Dopo (11.0.0):

  $errorHandler = new ErrorHandler();
  $errorHandler->disableErrorDisplay();
  $errorHandler->registerNonThrowableErrorHandler();
  $debugger = new Debugger();
  $debugger->startExecutionTimeCalculation();

Motivazione:

  • Eliminazione dello stato globale
    • Miglioramento della testabilità attraverso dependency injection
    • Conformità ai principi SOLID
    • Possibilità di iniettare logger custom conformi a PSR-3

Impatto: Il file Public/index.php deve essere aggiornato per creare istanze delle classi invece di usare metodi statici. Tutte le chiamate statiche a ErrorHandler e Debugger devono essere convertite a chiamate di istanza.

Azione richiesta:

  • Aggiornare il file Public/index.php per creare istanze di ErrorHandler e Debugger
    • Se si desidera utilizzare un logger custom (es. Monolog), iniettarlo nel costruttore di ErrorHandler
    • Verificare che non esistano altre chiamate statiche a queste classi nel codebase

Prima (10.x):

  $iv = Encryptor::createInizializationVector();

Dopo (11.0.0):

  $iv = Encryptor::createInitializationVector();

Motivazione: Correzione di un typo nel nome del metodo per migliorare la coerenza del codebase e facilitare l'uso dell'API.

Impatto: Questo è un potenziale breaking change se il metodo veniva chiamato direttamente nel codice utente. Il metodo è principalmente utilizzato internamente dal framework (in DataMapper per proprietà crittografate), ma potrebbe essere stato usato in codice custom per crittografia manuale.

Azione richiesta:

  • Cercare tutte le occorrenze di createInizializationVector nel proprio codebase
    • Sostituire con createInitializationVector (con la "t" invece della "z")
    • Verificare il corretto funzionamento delle operazioni di crittografia

✨ Miglioramenti

Questo facilita il debugging e rende più chiaro agli sviluppatori il motivo degli errori di configurazione.

🧪 Testing

Copertura totale: +603 linee di test per garantire affidabilità del nuovo pattern architetturale.

📝 Documentazione

🔄 Compatibilità

Questa è una major release (11.0.0) che introduce breaking changes. L'aggiornamento richiede modifiche al codice esistente:

📋 Checklist di Migrazione da 10.x a 11.0.0


[10.1.7] - 2025-12-21 - Correzione Bug buildPropertiesConditions e Test Suite

Questa patch release corregge un bug critico introdotto nella versione 10.1.0 nel metodo buildPropertiesConditions di DependentModel e SelfReferencedModel, che impediva il corretto override del metodo di BaseModel. Inoltre corregge errori sistematici nella test suite che utilizzavano nomi di proprietà in formato snake_case invece di camelCase.

🐛 Bug Fixes

Correzione Typo Nome Metodo buildPropertiesConditions

Corretto un errore di battitura nel nome del metodo introdotto nella versione 10.1.0 che impediva l'override corretto del metodo di BaseModel:

Scenario del bug:

  1. Nella versione 10.1.0 è stata introdotta la feature "Estensione Query Dinamiche ORM a Tutte le Proprietà"
  2. Il metodo in BaseModel si chiamava correttamente buildPropertiesConditions (plurale)
  3. Il metodo in DependentModel e SelfReferencedModel era stato erroneamente chiamato buildPropertyConditions (singolare)
  4. A causa del nome diverso, non avveniva l'override del metodo
  5. Questo causava due problemi critici:
Conseguenze del bug:

  • Per le proprietà ReferencedEntity, il quarto parametro ($isForeignKey) non veniva impostato a true
  • Questo impediva l'aggiunta automatica del suffisso _id ai nomi delle colonne foreign key
  • Per le proprietà builtin (string, int, bool, etc.), il bind type era erroneamente typeEntity invece del tipo corretto
  • Query SQL potenzialmente malformate e errori di binding dei parametri

Impatto della correzione:

  • Il metodo ora fa correttamente override, utilizzando l'implementazione specializzata per DependentModel/SelfReferencedModel
  • Il quarto parametro di appendCondition() viene passato correttamente: $propertyValue instanceof ReferencedEntity
  • I bind types vengono determinati dinamicamente tramite DataType::fromReflection() invece di essere hardcodati
  • Le query SQL vengono costruite correttamente con i suffissi _id per le foreign key

Correzione Test Suite: Convenzione Naming Proprietà

Corretti errori sistematici nella test suite che utilizzavano nomi di proprietà in formato snake_case (convenzione database) invece di camelCase (convenzione PHP):

Motivo del problema:

Esempio di correzione:

// ❌ PRIMA (errato - nome colonna database):
$posts = $postModel->getEntityCollectionByEntity(['author_id' => $user]);

// ✅ DOPO (corretto - nome proprietà PHP):
$posts = $postModel->getEntityCollectionByEntity(['author' => $user]);

Convenzione del framework:

  • Magic methods: getByAuthor($user) → genera internamente ['author' => $user] (camelCase)
  • Metodi espliciti: devono ricevere ['author' => $user] (camelCase), non ['author_id' => $user]
  • La conversione snake_case → camelCase avviene automaticamente solo nella costruzione delle query SQL

🧪 Testing

Nuovi Test per Prevenire Regressioni

Aggiunti 2 nuovi test in DependentModelTest.php che avrebbero catturato il bug della versione 10.1.0:

Copertura test totale:

  • 2 nuovi test aggiunti
  • 18 test esistenti corretti (convenzione naming)
  • Tutti i test ora passano correttamente

✅ Backward Compatibility

📊 Impatto

🎓 Note per gli Sviluppatori

Quando si utilizzano i metodi getEntityCollectionByEntity(), countEntityCollectionByEntity(), deleteEntityCollectionByEntity(), ricordare che le chiavi dell'array devono essere nomi di proprietà PHP in camelCase, non nomi di colonne database in snake_case:

// ✅ CORRETTO:
$posts = $postModel->getEntityCollectionByEntity([
    'author' => $user,           // nome proprietà PHP
    'category' => $category,     // nome proprietà PHP
    'isPublished' => true        // nome proprietà PHP
]);

// ❌ ERRATO:
$posts = $postModel->getEntityCollectionByEntity([
    'author_id' => $user,        // nome colonna database - causerà ReflectionException
    'category_id' => $category,  // nome colonna database - causerà ReflectionException
    'is_published' => true       // nome colonna database - causerà ReflectionException
]);

La conversione da camelCase (proprietà PHP) a snake_case (colonne database) avviene automaticamente all'interno del framework tramite NotationManager.

[10.1.6] - 2025-12-20 - Hotfix Costante LOG_DIRECTORY_PATH

Questa patch release corregge un bug critico introdotto nella versione 10.1.5 relativo alla definizione ricorsiva della costante LOG_DIRECTORY_PATH nel file di configurazione.

🐛 Bug Fixes

Correzione Definizione Ricorsiva LOG_DIRECTORY_PATH

Corretto un bug introdotto nella versione 10.1.5 che causava una definizione ricorsiva della costante LOG_DIRECTORY_PATH:

    // Pattern di sostituzione corretto (linee 115-116):
    "const LOG_DIRECTORY_PATH = SYSTEM_PATH . APPLICATION_PATH . LOGS . DIRECTORY_SEPARATOR;",
    "const LOG_DIRECTORY_PATH = ROOT_PATH . LOGS . DIRECTORY_SEPARATOR;",
    $this->assertStringContainsString(
        "const LOG_DIRECTORY_PATH = ROOT_PATH . LOGS . DIRECTORY_SEPARATOR;", 
        $content
    );

Scenario del bug:

  1. Nella versione 10.1.5, la costante LOG_DIRECTORY_PATH era definita usando se stessa: ... . LOG_DIRECTORY_PATH
  2. Questo causava una definizione ricorsiva non valida che avrebbe potuto generare errori a runtime
  3. Il bug era presente sia nel file di configurazione del framework che nel processo di installazione
Impatto della correzione:

  • Il percorso dei log viene ora costruito correttamente utilizzando DIRECTORY_SEPARATOR
  • Sia il file Config/config.php del framework che il processo di installazione automatica utilizzano la definizione corretta
  • I test verificano che la sostituzione durante l'installazione funzioni correttamente

✅ Backward Compatibility

📊 Impatto

[10.1.5] - 2025-12-20 - Correzione Configurazione Framework Post-Installazione

Questa patch release corregge un problema nel processo di installazione automatica che non modificava correttamente alcune costanti del file di configurazione framework nella root del progetto.

🐛 Bug Fixes

Aggiornamento Automatico Costanti in configFramework.php

Corretto il processo di installazione per aggiornare automaticamente le costanti del file configFramework.php creato nella root del progetto durante l'installazione:

1. APPLICATION:

2. REFERENCE_CACHE_DIRECTORY:

3. LOG_DIRECTORY_PATH:

4. MODULE_FOLDERS:

Scenario del problema:

  1. Utente esegue: php SismaFramework/Console/sisma install MyProject
  2. Il file Config/configFramework.php veniva creato nella root del progetto
  3. La costante PROJECT veniva aggiornata correttamente, ma APPLICATION, REFERENCE_CACHE_DIRECTORY, LOG_DIRECTORY_PATH e MODULE_FOLDERS mantenevano i valori del framework originale
  4. Questo causava percorsi errati per cache e log, e riferimenti all'applicazione 'Sample' invece di 'Application'
Dopo la correzione:

  • Il file configFramework.php ha i valori corretti per un nuovo progetto
  • I percorsi di cache e log puntano alla root del progetto invece che al framework
  • L'applicazione è correttamente identificata come 'Application'
  • L'array MODULE_FOLDERS è vuoto, pronto per essere popolato dall'utente

🧪 Testing

Aggiornamento Test InstallationManager

Aggiornati i test per verificare le nuove modifiche al processo di installazione:

- Verifica const APPLICATION = 'Application'

- Verifica const REFERENCE_CACHE_DIRECTORY = ROOT_PATH . CACHE . DIRECTORY_SEPARATOR; - Verifica const LOG_DIRECTORY_PATH = ROOT_PATH . LOGS . LOG_DIRECTORY_PATH; - Verifica const MODULE_FOLDERS = [];

- Aggiunge costanti APPLICATION = 'Sample'

- Aggiunge costanti CACHE, LOGS, SYSTEM_PATH, APPLICATION_PATH, ROOT_PATH - Aggiunge REFERENCE_CACHE_DIRECTORY e LOG_DIRECTORY_PATH con valori originali del framework - Aggiunge MODULE_FOLDERS con 'SismaFramework' nel array - Questo permette ai test di verificare che la trasformazione avvenga correttamente

✅ Backward Compatibility

📊 Impatto

[10.1.4] - 2025-12-14 - Correzioni Installazione e Aggiornamento Test Suite PHPUnit

Questa patch release corregge un bug nel processo di installazione automatica e aggiorna la test suite per conformità alle best practice di PHPUnit 11+ eliminando deprecation notices relative all'uso di mock al posto di stub.

🐛 Bug Fixes

Correzione Riferimento File Configurazione in Installazione

Corretto il processo di installazione automatica per rinominare correttamente il riferimento al file di configurazione in Public/index.php:

    $patterns = [
        "dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Config'",
        "dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Autoload'",
        "'Config' . DIRECTORY_SEPARATOR . 'config.php'",  // ← NUOVO
    ];
    $replacements = [
        "dirname(__DIR__) . DIRECTORY_SEPARATOR . 'SismaFramework' . DIRECTORY_SEPARATOR . 'Config'",
        "dirname(__DIR__) . DIRECTORY_SEPARATOR . 'SismaFramework' . DIRECTORY_SEPARATOR . 'Autoload'",
        "'Config' . DIRECTORY_SEPARATOR . 'configFramework.php'",  // ← NUOVO
    ];

Scenario del bug:

  1. Utente esegue: php Console/sisma install MyProject
  2. Il file Public/index.php veniva copiato e aggiornato per i path di Autoload e Config
  3. Tuttavia, il riferimento al file di configurazione rimaneva config.php invece di configFramework.php
  4. L'applicazione non riusciva a trovare il file di configurazione causando errori fatali
Dopo la correzione:

  • Il file Public/index.php include correttamente Config/configFramework.php
  • L'installazione automatica produce un progetto immediatamente funzionante

🧪 Testing

Aggiornamento Test Suite per PHPUnit 11+

Aggiornati tutti i file di test per utilizzare createStub() al posto di createMock() quando non vengono configurate aspettative (expectations), eliminando deprecation notices introdotte in PHPUnit 11:

InstallationCommandTest.php: $mockInstallationManager$installationManagerStub

* ScaffoldCommandTest.php: $mockScaffoldingManager$scaffoldingManagerStub * ScaffoldingManagerTest.php: $configMock$configStub (2 occorrenze) * InstallationManagerTest.php: Rimossi setAccessible(true) deprecati (PHP 8.1+)

* BaseFixtureTest.php: $configMock$configStub, $dataMapperMock → stub

* BaseFormTest.php: $configMock$configStub, $dataMapperMock → stub, $requestMock → stub * FilterTypeTest.php, AutoloaderTest.php, ConfigTest.php, DebuggerTest.php, DispatcherTest.php * EncryptorTest.php, FilterTest.php, FixturesManagerTest.php, LoggerTest.php * ModuleManagerTest.php, NotationManagerTest.php

* AdapterMysqlTest.php, BaseEntityTest.php, BaseModelTest.php, SismaCollectionTest.php

* DependentModelTest.php, ReferencedEntityTest.php, SelfReferencedEntityTest.php, SelfReferencedModelTest.php * CacheTest.php, DataMapperTest.php, ErrorHandlerTest.php, ProcessedEntitiesCollectionTest.php, QueryTest.php * JoinEagerLoadingTest.php, ResultSetMysqlTest.php

* BasePermissionTest.php, BaseVoterTest.php, AuthenticationTest.php
    // ❌ Prima (PHPUnit 11 deprecation warning):
    $configMock = $this->createMock(Config::class);
    $configMock->expects($this->any())  // ← expects() su stub non necessario
            ->method('__get')
            ->willReturnMap([...]);
    
    // ✅ Dopo (conforme PHPUnit 11+):
    $configStub = $this->createStub(Config::class);
    $configStub->method('__get')  // ← solo configurazione valori di ritorno
            ->willReturnMap([...]);

🔧 Refactoring

Pulizia Codice InstallationManager

Refactorizzata formattazione del codice in InstallationManager.php per migliorare leggibilità:

Impatto: Nessun cambiamento funzionale, solo miglioramento della manutenibilità.

✅ Backward Compatibility

📊 Metriche

[10.1.3] - 2025-12-10 - Correzione Parsing Argomenti CLI

Questa patch release corregge un bug critico nel sistema di parsing degli argomenti posizionali dei comandi CLI che impediva il corretto funzionamento del comando install.

🐛 Bug Fixes

Parsing Dinamico Argomenti Posizionali CLI

Corretto bug nel CommandDispatcher che utilizzava nomi hardcodati per gli argomenti posizionali, causando incompatibilità tra comandi diversi:

Scenario del bug:

  1. Utente esegue: php Console/sisma install MyProject
  2. CommandDispatcher assegnava l'argomento come ['entity' => 'MyProject']
  3. InstallationCommand cercava getArgument('projectName')null
  4. Il comando falliva con errore "Project name is required"
Dopo la correzione:

  1. CommandDispatcher assegna: ['0' => 'MyProject']
  2. InstallationCommand legge getArgument('0')'MyProject'
  3. Il comando funziona correttamente

🧪 Testing

✅ Backward Compatibility

📊 Impatto

[10.1.2] - 2025-12-10 - Normalizzazione Gestione Slash nei Path

Questa patch release migliora la robustezza della gestione dei path nel router attraverso la normalizzazione automatica degli slash, eliminando potenziali bug da doppi slash o slash mancanti.

🔧 Refactoring

Correzioni PHPStan per Qualità del Codice

Risolti warning di analisi statica segnalati da PHPStan per migliorare la qualità e la correttezza del codice:

Normalizzazione Automatica Slash in Router

Migliorata la gestione dei path nel Router per rendere più robusta e consistente la concatenazione degli URL:

- ❌ Prima: La responsabilità di aggiungere / era del chiamante (concatenateMetaUrl('/path'))

- ✅ Dopo: Il metodo normalizza automaticamente il path (concatenateMetaUrl('path')/path)

- Idempotenza: rtrim() rende l'operazione sempre sicura

- Prevenzione doppi slash: eliminati potenziali path malformati come /meta//url - API più intuitiva: non serve più passare / manualmente

- Prima: Router::concatenateMetaUrl('/' . $this->pathController)

- Dopo: Router::concatenateMetaUrl($this->pathController)

🧪 Testing

✅ Backward Compatibility

📊 Impatto

[10.1.1] - 2025-12-06 - Supporto HTTP Range Requests e Miglioramenti API Response

Questa patch release corregge un bug critico di conformità agli standard HTTP che impediva la riproduzione di video in Safari. Implementato il supporto completo per HTTP Range Requests (RFC 7233) con gestione di 206 Partial Content e 416 Range Not Satisfiable. Migliorata l'API della classe Response con constructor injection.

🐛 Bug Fixes

Supporto HTTP Range Requests per Streaming Media

Corretto bug critico nel serving di file statici che causava la mancata riproduzione di video in Safari:

Scenario del bug:

  1. Safari richiede un video con header Range: bytes=0-1023
  2. Il server ignorava l'header e restituiva 200 OK con l'intero file
  3. Safari rifiutava di riprodurre il video
  4. Impossibilità di fare seek/skip nei file multimediali
Casi d'uso risolti:

🎨 Refactoring

Response Constructor Injection

Migliorata l'API della classe Response:

🧪 Testing

🔧 Dettagli Tecnici

[10.1.0] - 2025-12-02 - Strumenti CLI per Scaffolding, Installazione e Rifatorizzazione Dispatcher

Benvenuti alla release 10.1.0, una delle più ricche di novità nella storia del framework! Utility CLI rivoluzionano il flusso di sviluppo quotidiano, con scaffolding automatico e installazione guidata che accelerano drasticamente la creazione di nuovi progetti. Ottimizzato profondamente il Dispatcher attraverso una rifatorizzazione completa seguendo i principi SOLID, separando le responsabilità in sette classi specializzate che rendono il codice più manutenibile e testabile.

Nascono nuove funzionalità per l'ORM: le funzioni di aggregazione SQL (AVG, MAX, MIN, SUM) permettono ora query analitiche avanzate con supporto per DISTINCT, alias, subquery e aggregazioni multiple, mentre l'estensione del sistema di query dinamiche con metaprogrammazione a tutte le proprietà (non più solo entità referenziate) riduce drasticamente la necessità di scrivere metodi repository ripetitivi generando automaticamente query type-safe, e il supporto completo per JOIN SQL con eager loading gerarchico multi-entità risolve definitivamente il problema N+1 delle query supportando relazioni nested a più livelli con dot notation e sintassi array.

Comandi CLI di scaffolding generano automaticamente l'intero stack CRUD (Controller, Model, Form, Views) a partire da un'Entity esistente, mentre il sistema di installazione configura progetti completi in pochi secondi. Oltre 400 linee di nuovi test garantiscono una copertura completa di tutte le nuove funzionalità, assicurando robustezza e affidabilità.

Molto è stato fatto anche sul fronte architetturale: la rifatorizzazione del Dispatcher riduce la complessità ciclomatica da oltre 400 linee a meno di 200, creando sette nuovi file di helper classes che gestiscono routing, factory dei controller, parsing degli argomenti e gestione delle risorse statiche. Pattern consolidati come Command/Manager vengono applicati sistematicamente ai comandi CLI, con dependency injection e gestione centralizzata delle eccezioni.

Livello enterprise raggiunto con le funzioni di aggregazione ORM: AVG, MAX, MIN e SUM supportano ora DISTINCT, alias personalizzati, subquery e aggregazioni multiple sulla stessa query, portando l'ORM a competere con i framework più evoluti. Estesa significativamente anche la documentazione, con due nuove guide complete per scaffolding e installazione automatica.

Architettura completamente rinnovata: il Dispatcher è stato suddiviso in sette classi specializzate (RouteResolver, ControllerFactory, ActionArgumentsParser, ResourceHandler, RouteInfo, FixturesManager, ResourceMaker) che seguono i principi SOLID e facilitano manutenibilità ed estensibilità future.

Notevole il numero di miglioramenti introdotti in questa release, che rappresenta un punto di svolta nella maturità del framework. Dedichiamo questa versione a tutti gli sviluppatori che quotidianamente utilizzano SismaFramework per creare applicazioni robuste e scalabili.

Nuove possibilità si aprono con questi strumenti professionali: template personalizzabili per lo scaffolding, configurazione database da CLI, protezione contro sovrascritture accidentali e auto-detection intelligente del tipo di Model più appropriato.

Ottima base per futuri sviluppi: questa release pone le fondamenta per ulteriori miglioramenti al sistema di scaffolding e all'ORM, con possibilità di estensione illimitate che verranno esplorate nelle prossime versioni.

Finalmente, dopo mesi di lavoro intenso, possiamo dire che il framework ha raggiunto un livello di maturità che lo rende adatto anche a progetti complessi e mission-critical.

Ricordiamo che questa release è completamente retrocompatibile e l'aggiornamento è fortemente consigliato a tutti gli utenti della versione 10.0.x per beneficiare di questi importanti miglioramenti.

Articolata in tre aree principali (CLI Tools, Architettura, ORM), questa release rappresenta un passo significativo nell'evoluzione del framework, portando strumenti professionali di livello enterprise alla portata di tutti gli sviluppatori PHP.

✨ Nuove Funzionalità

Esempio di utilizzo:

  php SismaFramework/Console/sisma scaffold Product Catalog
  php SismaFramework/Console/sisma scaffold User Blog --type=DependentModel --force

Esempio di utilizzo:

  php SismaFramework/Console/sisma install MyProject
  php SismaFramework/Console/sisma install BlogPersonale --db-host=localhost --db-name=blog_db --db-user=root --db-pass=secret

Esempi di utilizzo:

  // PRIMA (solo entità referenziate):
  $model->getEntityCollectionByEntity(['referenced_entity' => $entity]);

  // ADESSO (qualsiasi proprietà):
  $users = $userModel->getByStatus(UserStatus::ACTIVE);
  $count = $productModel->countByPrice(99.99);
  $model->deleteByEmail('test@example.com');

  // Proprietà multiple con AND logico:
  $products = $productModel->getByNameAndCategory('iPhone', $electronics);

  // Con searchKey e paginazione:
  $articles = $articleModel->getByAuthor($author, 'keyword', ['date' => 'DESC'], 0, 20);

  // Query gerarchiche (SelfReferencedModel):
  $subCategories = $categoryModel->getByParentAndActive($parent, true);

  // Valori null su proprietà nullable:
  $orphans = $entityModel->getByNullableParent(null); // WHERE nullable_parent IS NULL

🏗️ Architettura

Vantaggi della rifatorizzazione:

🧪 Testing

📝 Documentazione

🚀 ORM

Esempio di utilizzo:

  // Media dei prezzi
  $query->setAVG('price', 'average_price');

  // Somma con DISTINCT
  $query->setSum('amount', 'total', distinct: true);

  // Multiple aggregazioni
  $query->setMin('price', 'min_price')
        ->setMax('price', 'max_price', append: true)
        ->setAVG('price', 'avg_price', append: true);

Esempi di utilizzo:

  // Many-to-one: eager loading con JOIN
  $articles = $articleModel->getEntityCollectionWithRelations(['author', 'category']);
  foreach ($articles as $article) {
      echo $article->author->name; // Già caricato, nessuna query N+1
  }

  // One-to-many: eager loading con batch loading
  $authors = $authorModel->getEntityCollectionWithRelations(['articleCollection']);

  // Relazioni nested multi-livello (dot notation)
  $articles = $articleModel->getEntityCollectionWithRelations(['author.country.continent']);

  // Relazioni nested (sintassi array)
  $articles = $articleModel->getEntityCollectionWithRelations([
      'author' => ['country', 'publisher' => ['city']]
  ]);

  // SelfReferencedEntity (tree structures)
  $categories = $categoryModel->getEntityCollectionWithRelations([
      'parentCategory',     // Padre
      'sonCollection'       // Figli
  ]);

  // Con parametri aggiuntivi
  $products = $productModel->getEntityCollectionWithRelations(
      relations: ['category', 'brand'],
      searchKey: 'laptop',
      order: ['price' => 'ASC'],
      limit: 20,
      joinType: JoinType::inner
  );

🧪 Testing

🔧 Miglioramenti Interni

[10.0.7] - 2025-11-17 - Correzione Bug SismaCollection

Questa patch release corregge un bug critico nella gestione delle entità persistenti all'interno delle SismaCollection.

🐛 Bug Fixes

Correzione Inserimento Entità Persistenti in SismaCollection

Corretto un bug nel metodo addOrUpdateIntoEntityCollection() della classe ReferencedEntity che causava errori durante l'inserimento in una SismaCollection di entità già persistenti dopo entità non ancora salvate:

    // Prima (bug):
    if (isset($entity->id) && ($includedEntity->id === $entity->id)) {
        $includedEntity = $entity;
        $found = true;
    }
    
    // Dopo (corretto):
    if (isset($entity->id) && isset($includedEntity->id) && ($includedEntity->id === $entity->id)) {
        $includedEntity = $entity;
        $found = true;
    }

Scenario del bug:

  1. Una SismaCollection contiene un'entità non ancora salvata (con id = null)
  2. Si tenta di aggiungere un'entità già persistente (con id valorizzato)
  3. Il confronto null === 123 falliva, ma il controllo isset() mancava per $includedEntity->id
  4. Questo poteva causare comportamenti imprevisti nell'aggiornamento della collection
Impatto: Risolve problemi di inconsistenza nelle SismaCollection quando si mescolano entità persistite e non persistite.

🧪 Testing

[10.0.6] - 2025-11-07 - Refactoring Filter e Documentazione Migrazione

Questa patch release migliora la qualità del codice della classe Filter attraverso l'eliminazione di duplicazioni e il riordino dei metodi secondo i principi del Clean Code. Include inoltre la documentazione per la migrazione dalla versione 9.x alla 10.x.

🔧 Refactoring

Eliminazione Duplicazione Codice in Filter.php

Refactorizzata la classe Filter per eliminare codice duplicato nei metodi di validazione con limiti di lunghezza:

    public function isMinLimitString($value, int $minLimit): bool
    {
        $result = true;
        $result = ($this->isString($value)) ? $result : false;
        $result = (strlen($value) >= $minLimit) ? $result : false;
        return $result;
    }
    public function isMinLimitString($value, int $minLimit): bool
    {
        return $this->isMinLengthForValidator($value, $minLimit, fn($v) => $this->isString($v));
    }

    private function isMinLengthForValidator(mixed $value, int $minLimit, callable $validator): bool
    {
        return $validator($value) && strlen($value) >= $minLimit;
    }

📚 Documentazione

Aggiunta Guida Migrazione 9.x → 10.x

Introdotto il file UPGRADING.md con documentazione completa per la migrazione:

📊 Metriche

✅ Backward Compatibility

[10.0.5] - 2025-11-01 - Refactoring Architetturale DataMapper

Questa patch release rifattorizza il DataMapper monolitico introducendo una separazione delle responsabilità in classi dedicate, seguendo i principi SOLID e Clean Code.

🏗️ Architettura

Refactoring DataMapper: Da Monolite a Separazione delle Responsabilità

Suddiviso il DataMapper monolitico (420 righe) in componenti specializzati per migliorare manutenibilità e testabilità:

- Metodi: start(), commit(), rollback()

- Flag di stato transazione centralizzato - Testabile indipendentemente

- Metodi: find(), findFirst(), getCount(), setVariable()

- Logica cache isolata e riutilizzabile - Parametro esplicito bool $ormCacheEnabled passato ai metodi invece di dereferenziare proprietà

- Elimina duplicazione del metodo privato getType()

- Riutilizzabile in altri contesti del framework

    public function __construct(
        ?BaseAdapter $adapter = null,
        ?ProcessedEntitiesCollection $processedEntityCollection = null,
        ?Config $config = null,
        private TransactionManager $transactionManager = new TransactionManager(),
        private QueryExecutor $queryExecutor = new QueryExecutor()
    )

🔧 Miglioramenti Interni

✅ Backward Compatibility

📊 Metriche

[10.0.4] - 2025-10-22 - Miglioramenti Qualità Codice e Correzione Dispatcher

Questa patch release corregge un bug importante nella gestione del routing.

🐛 Bug Fixes

Correzione Impostazione URL nel Router

Corretto il momento in cui viene impostato l'URL attuale nel Router all'interno del Dispatcher:

Impatto: Previene l'impostazione di URL per azioni non valide, migliorando la precisione del routing e la gestione degli errori 404.

[10.0.3] - 2025-10-08 - Hotfix Test Suite

Questa hotfix release corregge i test rotti nella versione 10.0.2.

🐛 Bug Fixes

Ripristino Mock BaseAdapter nei Test con DataMapper Reale

Ripristinati i mock di BaseAdapter nei test che istanziano DataMapper con costruttore reale:

Causa del problema: Questi test creano istanze di DataMapper con costruttore (non completamente mockato), che a sua volta istanzia Query, il cui costruttore chiama BaseAdapter::getDefault(). Senza il mock, getDefault() ritorna null causando errori Call to a member function getAdapterClass() on null.

Soluzione: Ripristinato BaseAdapter::setDefault($baseAdapterMock) in questi test specifici.

✅ Test Suite Finale

Mock rimossi con successo (14 test):

Mock mantenuti (7 test + 3 specifici ORM):

[10.0.2] - 2025-10-08 - Ottimizzazione Connessione Database [RITIRATA]

⚠️ NOTA: Questa versione è stata ritirata a causa di test rotti. Utilizzare la versione 10.0.3 invece.

Questa patch release ottimizza significativamente le performance eliminando connessioni al database non necessarie attraverso l'implementazione del lazy loading in BaseAdapter.

🚀 Performance

Lazy Loading della Connessione Database

Implementato lazy loading della connessione al database in BaseAdapter per evitare connessioni inutili:

Impatto sulle performance:

🧪 Testing

Semplificazione Test Suite

Rimossi 21 mock di BaseAdapter non più necessari grazie al lazy loading:

Impatto: Test più puliti e leggibili, eliminando boilerplate di setup per il mock del database.

✅ Backward Compatibility

[10.0.1] - 2025-09-25 - Correzione Bug Router

Questa patch release corregge un bug nella generazione degli URL con il Router.

🐛 Bug Fixes

Correzione Generazione URL con Parametri Query String

Corretto il metodo Router::makeCleanUrl() per gestire correttamente i parametri query string:

Esempio:

// Prima (bug):
Router::makeCleanUrl('/search', ['q' => 'test', 'page' => 2])
// Output errato: /search?q=test?page=2

// Dopo (corretto):
Router::makeCleanUrl('/search', ['q' => 'test', 'page' => 2])
// Output corretto: /search?q=test&page=2

Impatto: Risolve problemi di URL malformati quando si passano parametri query string al Router.

[10.0.0] - 2025-09-15 - Release Maggiore con Breaking Changes

Questa major release introduce breaking changes significativi per migliorare la qualità del codice e l'architettura del framework.

💥 Breaking Changes

1. CallableController::checkCompatibility() è ora statico

Motivazione: Il metodo checkCompatibility() non dovrebbe dipendere dallo stato dell'istanza del controller.

class MyController extends BaseController implements CallableController
{
    public function checkCompatibility(array $arguments): bool
    {
        return count($arguments) === 2;
    }
}
class MyController extends BaseController implements CallableController
{
    public static function checkCompatibility(array $arguments): bool
    {
        return count($arguments) === 2;
    }
}

Azione richiesta: Aggiungere la keyword static alla firma del metodo checkCompatibility() in tutti i controller che implementano CallableController.


2. Rimozione dell'interfaccia CrudInterface

Motivazione: L'interfaccia CrudInterface non forniva valore aggiunto rispetto a BaseController e creava confusione.

class PostController extends BaseController implements CrudInterface
{
    // Implementazione
}
class PostController extends BaseController
{
    // Implementazione (nessuna modifica ai metodi)
}

Azione richiesta: Rimuovere implements CrudInterface dalla dichiarazione delle classi controller. Nessuna modifica ai metodi è necessaria.


3. Language::getFriendlyLabel() richiede file di localizzazione

Motivazione: Eliminare valori hardcoded e centralizzare le traduzioni in file di configurazione.

// Funzionava anche senza file di localizzazione
$label = Language::getFriendlyLabel('it');
// Output: "Italiano" (hardcoded)
// Richiede il file config/locales/it.json con:
// {
//   "language": {
//     "friendly_label": "Italiano"
//   }
// }
$label = Language::getFriendlyLabel('it');
// Output: "Italiano" (da file di configurazione)

Azione richiesta:

  1. Creare la directory config/locales/ se non esiste
  2. Per ogni lingua supportata, creare un file JSON (es. it.json, en.json)
  3. Aggiungere la struttura richiesta con il nome della lingua
Esempio di file di localizzazione:

config/locales/it.json:

{
  "language": {
    "friendly_label": "Italiano"
  }
}

config/locales/en.json:

{
  "language": {
    "friendly_label": "English"
  }
}

🚀 Miglioramenti

📚 Migrazione

Per una guida completa alla migrazione dalla versione 9.x alla 10.x, consultare il file UPGRADING.md.


Hai trovato un problema?

Se hai trovato errori o vuoi suggerire miglioramenti, apri una issue su GitHub.

Report Issue Edit on GitHub