Skip to content

Phpstan classlib - #28

Closed
tombrain wants to merge 128 commits into
henshingly:masterfrom
tombrain:phpstan-classlib
Closed

tombrain wants to merge 128 commits into
henshingly:masterfrom
tombrain:phpstan-classlib

Conversation

@tombrain

Copy link
Copy Markdown
Contributor

No description provided.

tombrain and others added 30 commits October 5, 2026 21:57
- Removed legacy ITX and IT_Error classes, replacing them with a new LMO_Template class that extends HTML_Template_IT.
- Updated init.php to utilize Composer's autoloader for dependency management.
- Modified lmo-showmain2.php to instantiate LMO_Template instead of HTML_Template_IT.
- Adjusted PHPUnit configuration to point to the correct vendor directory.
- Enhanced GoldenMaster tests to handle Composer dependencies more efficiently by creating symlinks for the vendor directory.
- Updated Normalizer to ignore vendor paths in include_path comparisons during tests.
- Implemented a new setup process for creating the first admin account if it doesn't exist.
- Added language translations for initial setup messages in Bosnian, Czech, German, English, Spanish, French, Croatian, Italian, Hungarian, Dutch, Norwegian, Portuguese, Romanian, and Slovene.
- Updated admin authentication and main admin pages to handle the new setup process.
- Removed the old installer and integrated the setup logic directly into the application flow.
… views and snapshots, enhancing user experience by eliminating unnecessary warnings. Additionally, add language options to the admin footer for improved accessibility and localization.
SP aus der Ligadatei kommt als String, setSpielEnde() nimmt aber nur int an.
Das Spielende blieb dadurch immer 0: Sonderpunkte fuer Verlaengerung und
Elfmeterschiessen wurden nicht beruecksichtigt, und ein anschliessendes
writeFile() schrieb SP=0 zurueck (Datenverlust).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sortTable() (liga) und ligaHandball::sortTable() werteten eine Gruppe
punktgleicher Teams erst aus, wenn danach ein Team mit anderer Punktzahl
folgte. Die letzte Gruppe der Tabelle wurde nie nach dem direkten Vergleich
sortiert.

Beide nutzen jetzt die gemeinsame Methode sortTiedGroups(). Sie berechnet
nur Gruppen neu, die kleiner als die ganze Tabelle sind; sonst wuerde
calcTableforTeams() -> sortTable() bei einem Unentschieden im direkten
Duell endlos rekursiv weiterrechnen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calcTableforTeams() buchte bei gTore == -2 die Punkte fuer die Niederlage
(PointsForLost) beim Gast statt beim Heimteam: Das Heimteam bekam sie als
Minuspunkte, der Gast als Pluspunkte (Kopierfehler aus dem Zweig darueber).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
factory() war nicht static; der dokumentierte Aufruf liga::factory($datei)
endet unter PHP 8 mit einem Error. Ausserdem wurde aus LigaType=Handball
der Klassenname "HandballLiga" gebildet, die Klasse heisst aber
ligaHandball; dadurch kam immer ein liga-Objekt zurueck. Ohne LigaType gab
es zusaetzlich eine Warnung (undefinierter Index).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Actual zaehlt ab 1, das Array $this->spieltage ab 0. Zurueckgegeben wurde
bisher der Spieltag nach dem aktuellen (bzw. null beim letzten Spieltag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Schleife las beim letzten Tabellenplatz $sort_pPkt[$i + 1] (undefinierter
Index), die Torsummen fuer den direkten Vergleich wurden ohne Initialisierung
hochgezaehlt und allPartieForTeams() kann null liefern. Unter PHP 8 brach
jede Tabellenberechnung einer Fussball-Liga mit Warnungen ab. Ausserdem
summierte der direkte Vergleich die Auswaertstore nicht (= statt +=), und
nach einem Tausch wurde die Position des Paares falsch gesetzt.

Die Regeln bleiben: Punkte, Spiele, Tordifferenz, Tore, dann direkter
Vergleich (Tore, Auswaertstore im direkten Vergleich, alle Auswaertstore).
sortDirectTable() gab statt einer Tabelle Debug-Text aus und liefert jetzt
die normale Sortierung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'spiele' zaehlte jede angesetzte Partie, auch noch nicht gespielte. Diese
nahmen ausserdem an der Suche nach hoechstem Heim-/Auswaertssieg und den
meisten Toren teil (mit -1:-1 als Ergebnis).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Spiele ohne Niederlage" wurde mit = statt .= angehaengt und ersetzte so
die vorherige Ausgabe (z. B. "2 Sieg(e)"); uebrig blieb nur die letzte Zeile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fehlte das Zeichen, wurde strrpos() false und substr($str, 1) schnitt nur
das erste Zeichen ab ("ohnepunkt" -> "hnepunkt"). Jetzt wie strBeforChar():
leerer String.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Die Endung wird ohne Gross-/Kleinschreibung erkannt, der Name aber mit
strrpos($data, 'l98') abgeschnitten. Bei "POKAL.L98" fand das nichts und
der Name wurde "POKAL.L9". Jetzt wird alles vor dem letzten Punkt genommen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
findImage() gibt ohne Treffer den Alternativtext zurueck. Ist er gesetzt,
brach die Schleife ueber CLASSLIB_IMG_TYPES schon nach der ersten Endung
(.gif) ab; Logos als .jpg oder .png wurden nie gefunden. Der Alternativtext
wird jetzt erst verwendet, wenn keine Endung passt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neue Testsuite "unit" (tests/Unit), laeuft vor den Golden-Master-Tests.
ClasslibTestCase laedt die classlib ohne init.php und baut Ligen im
Speicher oder als kleine .l98-Datei auf.

Abgedeckt: Hilfsfunktionen, team, sektion, optionsSektion, partie,
spieltag, liga (Nachschlagen, gamesSorted, loadFile inkl. Pokal mit Hin- und
Rueckspiel, calcTable mit Heim/Gast/Hin/Rueck, Strafen, Kegel, n.V./i.E.,
gruener Tisch, direkter Vergleich), ligaFussball, ligaHandball, factory(),
stats und html_output.php. Die Tests sichern die vorangehenden Bugfixes ab.

Nicht abgedeckt: writeFile() (bindet init.php ein) und loadFromDb() (braucht
eine Datenbank).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lmo-calctable.php verglich fuer die Gruppenbildung substr($tab0, 7, 9),
also nur die letzte Ziffer der Punkte plus die Minuspunkte. Teams mit z.B.
59 und 49 Punkten galten als gleichauf, wenn sie in der Tabelle direkt
untereinander standen; ihre Duelle flossen dann in den direkten Vergleich
der tatsaechlich punktgleichen Teams ein.

Nachgewiesen an der Primera Division 2013/14: Villarreal und Real Sociedad
(je 59) gerieten in die Gruppe von Valencia und Celta (je 49), Celta stand
dadurch faelschlich vor Valencia. Jetzt wird der komplette Punkte- und
Minuspunkteblock verglichen.
Die classlib wich bei Punktgleichheit vom Kern ab:

- Gleichstand wurde ueber Plus- und Minuspunkte erkannt, auch ohne
  Zwei-Punkte-Regel. Jetzt zaehlen die Minuspunkte nur bei MinusPoints=2.
- Brachte der direkte Vergleich keine Entscheidung (Teams haben noch nicht
  gegeneinander gespielt oder das Duell war ausgeglichen), ging die
  Reihenfolge nach Tordifferenz verloren; entschieden hat dann der
  Objektvergleich der Teams. Jetzt bleibt die bisherige Reihenfolge.
- Der direkte Vergleich wurde innerhalb des direkten Vergleichs erneut
  angewendet. Wie im Kern wird jetzt nur einmal ausgewertet.

Die Sortierung der Gleichstandsgruppe steht in compareDirect() (Punkte,
Minuspunkte, Tordifferenz, Tore; Kegel: Tore vor Tordifferenz), Handball
hat eine eigene Variante (Punkte, Spiele, Tordifferenz).

Gefunden mit echten Ligen (Serie A, Primera Division mit Wikipedia-
Abschlusstabellen): die classlib lag dort in 6 Saisons falsch.
Der Rang aus dem direkten Vergleich ersetzt in $tab0 die erste Ziffer der
Tordifferenz. Ab Rang 10 wurde er zweistellig geschrieben und ueberschrieb
dabei die letzte Ziffer der Minuspunkte. Mit Minuspunkten (Zwei-Punkte-
Regel) stand das beste Team der Gruppe dann ganz unten, z.B. in einer
Handball-Liga nach einem Spieltag mit lauter Unentschieden.

Der Rang belegt jetzt immer genau eine Stelle; ab 10 folgen die Zeichen
hinter "9" (":", ";", ...), die als String richtig dahinter sortieren.
Fuer Gruppen mit weniger als zehn Teams aendert sich $tab0 nicht.
Wertung "beidseitiges Erg." (ET=3): laut Hilfetext zaehlt das Ergebnis
fuer beide Mannschaften gleichermassen aus Sicht der Heimmannschaft. In der
Praxis ist das die Spielwertung gegen beide Mannschaften (z.B. beide nicht
angetreten), eingetragen als 0:2.

Bisher bekamen beide Teams nur die Tore und das Spiel, aber weder Sieg,
Remis oder Niederlage noch Punkte und Minuspunkte. In einer Liga mit
Minuspunkten standen beide dadurch besser da als richtig. Jetzt wird das
Ergebnis aus Heimsicht fuer beide gewertet, in der Tabelle und im direkten
Vergleich (lmo-calctable1.php).

Golden Master: geaendert sind die Ligen mit dieser Wertung (wertung,
direct_wertung, direct_wertung_many) und die Ergebniseingabe im
Admin-Test, die ein 1:1 beidseitig eintraegt. Spiele und Tore bleiben
gleich, es aendern sich Bilanz, Punkte, Minuspunkte, Serien und die
Reihenfolge.
Die Standardtabelle rechnet bis $endtab = $anzst, also bis zum letzten
Spieltag der Saison, auch wenn erst wenige Spieltage gespielt sind. Eine
Strafe "ab Spieltag 20" wurde dadurch schon nach Spieltag 5 abgezogen.

Massgeblich ist jetzt der letzte Spieltag mit einem Ergebnis
($calc_lastst): die Strafe zaehlt, wenn er (bzw. der angezeigte Spieltag,
falls frueher) mindestens X ist. Strafen ab Spieltag 0 zaehlen wie bisher
sofort. Heim- und Auswaertstabelle bleiben ohne Strafen.
Straf-/Bonustore gibt man in lmo-adminteams.php als "x : y" ein, gespeichert
werden sie negiert (TOR1 = -x, TOR2 = -y); "+2" bei den Gegentoren heisst
also zwei Gegentore mehr. strafen() zog TOR2 mit abs() ab und machte aus
zusaetzlichen Gegentoren weniger Gegentore. Jetzt wie lmo-calctable.php:
mTor -= TOR2.
Die classlib buchte Strafen innerhalb der Partien eines Spieltags:

- War das Team an Spieltag STDA spielfrei, entfiel die Strafe ganz.
- Strafen zaehlten auch in Heim- und Auswaertstabelle.
- Eine Strafe ab einem spaeteren Spieltag zaehlte schon, sobald die
  Tabelle bis dahin gerechnet wurde, auch ohne gespielte Partien.

Jetzt entscheidet strafeFaellig(), ob eine Strafe in dieser Tabelle zaehlt
(nicht in Heim-/Auswaertstabelle, Hin- und Rueckrunde wie lmo-calctable.php,
erst wenn Spieltag STDA Ergebnisse hat). calcTable() bucht faellige Strafen
nach jedem Spieltag genau einmal je Team, unabhaengig davon, ob es spielt;
so stimmt auch die Platzierung je Spieltag (possp).
Das PDF-Addon ist ein eigenes Projekt und klinkt sich ueber vier
Stellen in LMO ein. Drei davon pruefen, ob die Addon-Datei vorhanden
ist; todo=pdfoptions tat das nicht und endete ohne Addon mit einem
Fatal Error samt Dateipfad. Jetzt erscheint in diesem Fall die normale
Admin-Seite.

Der Snapshot der Seite enthaelt damit keine Fehlermeldung mehr.
- help/Deutsch/addons/readme.php erzeugte die Anleitung des PDF-Addons
  als PDF und war ohne das Addon nicht lauffaehig; pdf_addon.txt ist
  der Text dazu, ros.jpg wurde nirgends verwendet
- auskommentierte Einbindung der PDF-Klassen in classlib/ini.php
- lmo-paintgraph.php: keine Referenz im Projekt (die Fieberkurve
  zeichnet der Browser mit Chart.js) und nicht lauffaehig, weil die
  Schriftdatei aus dem PDF-Ordner fehlt

Die Hilfeseiten zu den extern erhaeltlichen Addons bleiben.
Mit core.autocrlf unter Windows schlug LigaLoadFileTest fehl: Der Test
baut seine Liga aus einem Textblock und ersetzt darin Stellen mit \n.
- lmo/composer.json war keine Composer-Datei, sondern die Laufzeit-Angaben
  des LMO: heisst jetzt lmo/version.json
- vendor/ liegt neben lmo/ statt darin; init.php, Docker-Webserver und
  Golden-Master-Instanzen finden den Autoloader dort
- pear/html_template_it in der stabilen Version 1.3.3 statt dev-master,
  damit sich das Paket per composer require installieren laesst
- Typ library, der LMO wird nur noch per Composer verteilt
composer require lmo/lmo_php8, danach kopiert vendor/bin/lmo-install den
LMO in den Ordner lmo (oder einen angegebenen) und traegt sich in die
composer.json des Projekts ein, damit composer update ihn dort aktuell haelt.

- Programmdateien werden ueberschrieben; in config, ligen, output, den
  Tipps und Spielerstatistiken werden nur fehlende Dateien angelegt
- Programmdateien, die es im Paket nicht mehr gibt, werden entfernt
- vendor/ bleibt ausserhalb des LMO-Ordners und wird per .htaccess gesperrt
- tests/, docker/ und .github/ gehoeren nicht zum Paket (export-ignore)
- README: Installation nur noch per Composer, der FTP-Upload entfaellt
- die installierte Version nennt Composer (Git-Tag des Pakets), ohne Tag
  zeigt der LMO "dev"; lmo/version.json enthaelt nur noch Lizenz und
  PHP-Mindestversion
- die neueste Version kommt von Packagist statt aus der Pruefdatei auf
  vest-sport.de: nur im Admin-Bereich fuer den Hauptadmin und hoechstens
  einmal am Tag, nicht mehr bei jedem Seitenaufruf
- die Update-Seite nennt composer update statt eines Download-Links
- Versionen werden als Versionsnummern verglichen, nicht als Text
- Snapshots der Info-Seite: Version als Platzhalter {LMO_VERSION}
- ohne allow_url_fopen wird nichts abgefragt und keine Merkdatei geschrieben:
  auf einem getaggten Stand haetten die Golden-Master-Tests sonst eine
  geaenderte Datei im Admin-Bereich gemeldet
- eine beschaedigte Merkdatei gilt als unbekannt
- der Test der installierten Version akzeptiert auch einen getaggten Stand
- bin/lmo-install sucht den Autoloader im Repo neben bin/ statt in lmo/vendor
- Typen fuer die Funktionen in lmo-version.php, Rueckgabewerte geprueft
- Monatsname May stand in drei Uebersetzungstabellen doppelt; im Kalender
  des Admin-Bereichs gewann dadurch im Mai die Abkuerzung
- liga::$options als Eigenschaft deklariert
- Klassennamen in lmo-rueckrunde.php in der Schreibweise der Klassen
- showDetails() gibt nichts zurueck; ?> mit Leerzeile am Dateiende entfernt
composer phpstan bzw. docker compose run --rm phpstan, in der Action als
eigener Job. Gesamter Code auf Stufe 0, neuer typisierter Code (src/,
bin/lmo-install, lmo/lmo-version.php) auf Stufe 8; geprueft wird fuer
PHP 8.0 bis 8.5.
- sortTable() und sortDirectTable() brachen bei einer Tabelle ohne Zeilen
  mit einem TypeError ab (array_multisort ohne Sortierspalten)
- getVerlaufTeam() und getVerlaufTeams() liefern fuer ein Team ausserhalb
  der Liga ein leeres Array statt einer Warnung
- calcStatsForTeams(), removePartie() und drei isset() auf Schleifen-
  schluessel vereinfacht, ohne Aenderung am Verhalten
Nur Kommentare, kein Code: $ vor den Parameternamen, Tippfehler in Typen
(interger, objekt, teamObject, char), und die tatsaechlichen Typen, wo der
Kommentar etwas anderes versprach (z. B. team|null statt object bei den
Suchfunktionen, Zeitstempel statt string bei Spieltag und Partie).
Die Hilfsskripte unter tests/GoldenMaster/bin bleiben auf Stufe 0: sie
binden den alten Code ein und arbeiten mit dessen globalen Variablen.
In den Tests vier Stellen praezisiert (Typangaben, Rueckruffunktion).
Vier Kommentare an das Verhalten angepasst: liga::factory() liefert null,
wenn es die Datei nicht gibt; pokalRunde::getPartien() und getSpieltage()
liefern je nach Aufruf ein Objekt, alle Eintraege oder false.
D1/D2 mit Text oder fehlenden Teilen (ab.cd.efgh, 23.08.) fuehrten in
loadFile() zu einem TypeError in checkdate(). Das Datum gilt jetzt als
fehlend (liga::dateToTime()).

Dazu ohne Aenderung am Verhalten: Teamnummern aus der Datei als Zahl an
teamForNumber(), strcmp() mit Strings, eine immer wahre Pruefung entfernt
und Typangaben in Kommentaren an die tatsaechlichen Werte angepasst.
@tombrain tombrain closed this Oct 11, 2026
@tombrain
tombrain deleted the phpstan-classlib branch October 11, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant