borisbaer: PHP function: Wie umgehe ich, dass eine Variable jedes Mal erneut definiert werden muss?

Hallo zusammen,

ich möchte verhindern, dass ich bei den im Bild unten dargestellten Funktionen jedes Mal die Variable $current erneut definieren muss. Meine Versuche, diese Variable von „außen“ einzuspeisen (wie es bspw. dieses Tutorial zeigt) waren leider nicht von Erfolg gekrönt.

Ich wäre für jede Hilfe dankbar!

  1. Hallo borisbaer,

    bitte stelle den Sourcecode als Text zur Verfügung. Für den Screenshot sind meine Augen ungeeignet.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo Rolf,

      klar doch:

      <?php
      
      // n a v . i n c
      
      function beginNav() { ?>
      
      	<nav id="page">
      
      <?php }
      
      function endNav() { ?>
      
      	</nav>
      
      <?php }
      
      function game() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === null || $current === 'game' ? 'current' : ''; ?>" href="game">Spiel</a>
      
      <?php }
      
      function releases() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'releases' ? 'current' : ''; ?>" href="releases">Releases</a>
      
      <?php }
      
      function merchandise() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'merchandise' ? 'current' : ''; ?>" href="merchandise">Me<span style="letter-spacing: .115em;">r</span>cha<span style="letter-spacing: .105em;">n</span>dise</a>
      
      <?php }
      
      function guides() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'guides' ? 'current' : ''; ?>" href="guides"><span style="letter-spacing: .105em;">Gu</span>ides</a>
      
      <?php }
      
      function emulation() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'emulation' ? 'current' : ''; ?>" href="emulation">Emula<span style="letter-spacing: .115em;">t</span>ion</a>
      
      <?php }
      
      function mods() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'mods' ? 'current' : ''; ?>" href="mods">Mods</a>
      
      <?php }
      
      function maps() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'maps' ? 'current' : ''; ?>" href="maps">Maps</a>
      
      <?php }
      
      function savegame() { if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; } ?>
      
      	<a class="<?= $current === 'savegame' ? 'current' : ''; ?>" href="savegame">S<span style="letter-spacing: .105em;">a</span>vegame</a>
      
      <?php }
      
    2. Hallo,

      bitte stelle den Sourcecode als Text zur Verfügung.

      das wäre mal ein Anfang, aber noch lange nicht ausreichend.

      Für den Screenshot sind meine Augen ungeeignet.

      Meine auch. In schwarz-blau-gelbem Pixelbrei eine Schrift mit etwa 1.5px Größe entziffern zu wollen, ist völlig hoffnungslos.

      Und darüber hinaus: Beschreibe bitte, was du eigentlich vorhast - also nicht technisch, sondern was du eigentlich erreichen willst. Das Folgeposting mit dem Quellcode gibt leider keinerlei Aufschluss, wofür der Code gut sein soll oder was er eigentlich tun soll.

      Einen schönen Tag noch
       Martin

      --
      Мир для України.
      1. Hallo Martin,

        sorry, wenn das nicht klar geworden ist.

        Also, in dem obigen Quellcode definiere ich bei jeder function (außer den ersten beiden) die Variable $current:

        if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; }
        

        Das tue ich, weil ich im Folgenden diese Variable benötige, nämlich bspw. an dieser Stelle:

        <a class="<?= $current === 'releases' ? 'current' : ''; ?>" href="releases">Releases</a>
        

        Wenn also der aktuelle URL-Parameter mit dem jeweiligen if clause für $current übereinstimmt, dann bekommt dieser Link die class current, wird also hervorgehoben.

        Der ganze Quellcode wird übrigens per php include eingefügt und wenn ich versuche, die Variable $current als globale Variable außerhalb der function zu definieren, dann kommt die Fehlermeldung, die Variable $current sei nicht definiert, sprich die function kommt nicht an diese globale Variable ran und ich frage mich, warum.

        Ich hoffe, das war etwas verständlicher.

        1. Hallo borisbaer,

          sorry, wenn das nicht klar geworden ist.

          alles gut - das ist ein typischer Fall von Betriebsblindheit: Du kennst dein Projekt, du kennst deinen Code, und du weißt, was du an welcher Stelle und mit welcher Methode erreichen willst. Andere wissen das nicht und müssen raten.

          Also, in dem obigen Quellcode definiere ich bei jeder function (außer den ersten beiden) die Variable $current:

          if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; }
          

          Das tue ich, weil ich im Folgenden diese Variable benötige, nämlich bspw. an dieser Stelle:

          <a class="<?= $current === 'releases' ? 'current' : ''; ?>" href="releases">Releases</a>
          

          Das schreit doch nach einer globalen Definition.

          Der ganze Quellcode wird übrigens per php include eingefügt und wenn ich versuche, die Variable $current als globale Variable außerhalb der function zu definieren, dann kommt die Fehlermeldung, die Variable $current sei nicht definiert, sprich die function kommt nicht an diese globale Variable ran und ich frage mich, warum.

          Hast du denn auch daran gedacht, sie mit dem Schlüsselwort global in die Funktion(en) zu importieren?

          Aber Funktionen, die auf globale Variablen zugreifen, sind sowieso unschön. Wie wär's, wenn du $current im globalen Scope definierst und als Parameter an die Funktionen übergibst, die diesen Wert brauchen? Dann wäre nur im globalen Scope, der ja sowieso alles zusammenklebt, ein bisschen "Magic", aber alle Funktionen wären autark.

          Einen schönen Tag noch
           Martin

          --
          Мир для України.
          1. Hallo Martin,

            Das schreit doch nach einer globalen Definition.

            Hast du denn auch daran gedacht, sie mit dem Schlüsselwort global in die Funktion(en) zu importieren?

            habe ich bisher nicht versucht, da hier einem davon abgeraten wird, wenn ich das richtig verstehe: Seit PHP 8.1.0 wird der Schreibzugriff auf das gesamte $GLOBALS-Array nicht mehr unterstützt.

            Aber Funktionen, die auf globale Variablen zugreifen, sind sowieso unschön. Wie wär's, wenn du $current im globalen Scope definierst und als Parameter an die Funktionen übergibst, die diesen Wert brauchen? Dann wäre nur im globalen Scope, der ja sowieso alles zusammenklebt, ein bisschen "Magic", aber alle Funktionen wären autark.

            Also, übergeben würde ich den Paramter dann so, oder?

            <?php
            
            function game($current) { ?>
            
            	<a class="<?= $current === null || $current === 'game' ? 'current' : ''; ?>" href="game">Spiel</a>
            
            <?php }
            

            Aber wo ist denn dieser global scope? Ich bin noch nicht wirklich sehr vertraut mit PHP. In JavaScript wäre er ja außerhalb aller functions, aber bei PHP habe ich keine Ahnung.

            1. Hallo,

              Hast du denn auch daran gedacht, sie mit dem Schlüsselwort global in die Funktion(en) zu importieren?

              habe ich bisher nicht versucht

              schade, denn das ist notwendig, um aus einer Funktion heraus auf globale Variablen zugreifen zu können.

              da hier einem davon abgeraten wird, wenn ich das richtig verstehe: Seit PHP 8.1.0 wird der Schreibzugriff auf das gesamte $GLOBALS-Array nicht mehr unterstützt.

              Möglicherweise hast du das missverstanden. Erstens geht es tatsächlich nur um das Schreiben auf $_GLOBALS, zweitens ist der direkte Zugriff auf $_GLOBALS ja etwas "unbequem", und wenn man schon globale Variablen verwenden möchte, finde ich das explizite Bekanntmachen innerhalb der Funktion mit dem global-Keyword sauberer. Dann darfst du auch schreibend darauf zugreifen (auch wenn das bei mir immer ein bisschen Zähneknirschen hervorruft).

              Aber Schreibzugriff brauchst du ja in deinem Fall auch gar nicht.

              Aber Funktionen, die auf globale Variablen zugreifen, sind sowieso unschön. Wie wär's, wenn du $current im globalen Scope definierst und als Parameter an die Funktionen übergibst, die diesen Wert brauchen? Dann wäre nur im globalen Scope, der ja sowieso alles zusammenklebt, ein bisschen "Magic", aber alle Funktionen wären autark.

              Also, übergeben würde ich den Paramter dann so, oder?

              <?php
              
              function game($current) { ?>
              
              	<a class="<?= $current === null || $current === 'game' ? 'current' : ''; ?>" href="game">Spiel</a>
              
              <?php }
              

              Genau. Und beim Aufruf der Funktion game() dann auch den Wert übergeben.

              Aber wo ist denn dieser global scope?

              Das ist sozusagen alles, was sich außerhalb aller Funktionen und Klassen abspielt.

              Ich bin noch nicht wirklich sehr vertraut mit PHP. In JavaScript wäre er ja außerhalb aller functions, aber bei PHP habe ich keine Ahnung.

              Das ist in PHP ganz genauso. Der Unterschied ist, dass eine Funktion in Javascript auf globale Daten zugreifen darf, ohne dass man das extra regeln müssten; in PHP muss man es explizit erlauben.

              Einen schönen Tag noch
               Martin

              --
              Мир для України.
              1. Hallo,

                Das schreit doch nach einer globalen Definition.

                wenn man schon globale Variablen verwenden möchte, finde ich das explizite Bekanntmachen innerhalb der Funktion mit dem global-Keyword sauberer

                Abe es ist immer noch der Unterschied zwischen Ölschmier an den Fingern und triefendem Schmodder. $_GLOBALS sollte man einfach ignorieren. Und globale Variablen dringend vermeiden.

                Ich muss aber auch eingestehen, dass es in PHP ohne radikale Objektorientierung mühsam ist, auf globale Variablen zu verzichten.

                $current === null || $current === 'game' ? 'current' : ''

                Hier würde ich definitiv dazu raten, $current bei der Initialisierung gleich auf 'game' zu setzen, wenn nichts drinsteht. Die Funktionen für die Menüpunkte werden von irgendwas aufgerufen. Dieses irgendwas sollte selbst eine Funktion sein (build_menu oder ähnlich), und in build_menu sollte sowas stehen wie

                   $current = $_GET['subpage'] ?? 'game';  // <-- NULL gar nicht erst entstehen lassen!
                   $menus = [ 'game', 'releases', 'merchandise', ...., 'savegame' ];
                   if (!in_array($current, $menus))
                      $current = 'game';
                   for ($menus as $menu) {
                      ("menu_$menu)($current == $menu);
                   }
                   ...
                
                   function menu_game($isCurrent)
                   {
                ?>
                     <a <?= $isCurrent ? 'aria-current="page"' : '' ?> href='...'>Game</a>
                <?php
                   }
                

                Der Funktionsaufruf mit ("menu_$menu)() funktioniert ab PHP 7.

                • Name der Funktion ist vom externen Namen entkoppelt. Wenn auch nur leicht. Noch besser wäre eine echte Map.
                • Subpage wird geprüft, ob es ein gültiger Name ist
                • Prüfung, ob der Menüpunkt die aktuelle Seite ist, ist zentralisiert und aus den Menüfunktionen entfernt

                Mit der Map meine ich:

                   $menus = [ 'game' => 'menu_game',
                              'releases' => 'menu_releases',
                              'merchandise' => 'menu_mechandise',
                              ....,
                              'savegame' => 'menu_savegame' 
                            ];
                   if (!array_key_exists($current, $menus))
                      $current = 'game';
                
                   for ($menus as $menuId => $menuHandler) {
                      $menuHandler($current == $menuId);
                   }
                

                Je länger man über etwas nachdenkt, desto mehr Abstraktionen fallen einem ein...

                Rolf

                --
                sumpsi - posui - obstruxi
        2. @@borisbaer

          if (!empty($_GET['subpage'])) { $current = $_GET['subpage']; }
          

          Das tue ich, weil ich im Folgenden diese Variable benötige, nämlich bspw. an dieser Stelle:

          <a class="<?= $current === 'releases' ? 'current' : ''; ?>" href="releases">Releases</a>
          

          Das Umkopieren von $_GET['subpage'] in eine Variable macht keinen Sinn. Du verwendest einfach bei jedem Vergleich $_GET['subpage'] und gut ist.

          Wenn also der aktuelle URL-Parameter mit dem jeweiligen if clause für $current übereinstimmt, dann bekommt dieser Link die class current, wird also hervorgehoben.

          Verwende aria-current="page".

          Eine Klasse current brauchst du dann nicht; du kannst den Attributselektor [aria-current="page"] zum Stylen verwenden. (Ein Klassenselektor ist auch nur eine andere Schreibweise für einen Attributselektor.)

          Was du nicht tun solltest: die aktuelle Seite verlinken. Stattdessen den Link auf den Hauptinhalt setzen (also genau dorthin, wo auch der Skip-Link hinführt).

          Live und in Schwarz/Weiß zu sehen, wo nie ein Mensch zuvor gewesen ist. Das PHP-Script, das die Seiten generiert, kann man sich auch ansehen.

          Ich hab letztens was darüber erzählt, zu sehen ist das auf Folie 7.

          Wenn es dir nicht behagt, wiederholt $_GET['subpage'] zu verwenden und damit immer wieder dasselbe zu tun, kannst du das auch mit einer Schleife machen; so in etwa:

          <?php
          $subpages = [
            [ 'title' => 'Spiel', 'path' => 'game' ],];
          ?>
          
          <nav>
            <ul>
          <?php foreach ($subpages as $subpage): ?>
              <li>
                <a
            <?php if ($subpage['path'] == $_GET['subpage']): ?>
                  href="#main"
                  aria-current="page"
            <?php elseif: ?>
                  href="$subpage['path']"
            <?php endif; ?>
                >
                  <?php echo htmlspecialchars($subpage['title']); ?>
                </a>
              </li>
          <?php endforeach; ?>
            </ul>
          </nav>
          

          🖖 Живіть довго і процвітайте

          --
          When the power of love overcomes the love of power the world will know peace.
          — Jimi Hendrix
          1. @@Gunnar Bittersmann

                    href="$subpage['path']"
            

            Na so’n Quatsch. Das sollte heißen:

                    href="<?php echo $subpage['path']; ?>"
            
                    <?php echo htmlspecialchars($subpage['title']); ?>
            

            Der Beispielcode ist inkonsistent.

            Wenn sichergestellt ist, dass der Inhalt von $subpages vollständig unter Kontrolle der Seitenautoren ist und da keine Daten von außerhalb reinkommen können, dann muss $subpage['title'] nicht durch htmlspecialchars() laufen.

            Sollte das jedoch nicht der Fall sein, dann muss auch $subpage['path'] abgesichert werden:

                    href="<?php echo htmlspecialchars($subpage['path']); ?>"
            

            🖖 Живіть довго і процвітайте

            --
            When the power of love overcomes the love of power the world will know peace.
            — Jimi Hendrix
          2. Hallo Gunnar,

            Das Umkopieren von $_GET['subpage'] in eine Variable macht keinen Sinn. Du verwendest einfach bei jedem Vergleich $_GET['subpage'] und gut ist.

            das habe ich anfangs sogar gemacht, aber dann wurde immer eine Fehlermeldung angezeigt, wenn der URL-Parameter subpage oben leer war, also bspw. http://games.local/games/demons-souls/ statt http://games.local/games/demons-souls/games. Die Fehlermeldung lautet:

            Warning: Undefined array key current

            Sobald ein URL-Parameter drin steht, funktioniert es wieder ohne Fehlermeldung.

            Verwende aria-current="page".

            Eine Klasse current brauchst du dann nicht; du kannst den Attributselektor [aria-current="page"] zum Stylen verwenden. (Ein Klassenselektor ist auch nur eine andere Schreibweise für einen Attributselektor.)

            Das werde ich auf jeden Fall ausprobieren, danke!

            Was du nicht tun solltest: die aktuelle Seite verlinken. Stattdessen den Link auf den Hauptinhalt setzen (also genau dorthin, wo auch der Skip-Link hinführt).

            Ich verstehe, das werde ich noch ändern.

            Live und in Schwarz/Weiß zu sehen, wo nie ein Mensch zuvor gewesen ist. Das PHP-Script, das die Seiten generiert, kann man sich auch ansehen.

            Ich hab letztens was darüber erzählt, zu sehen ist das auf Folie 7.

            Sehr interessant! Vielen Dank, dass ich mir das anschauen darf. Ich studiere das beizeiten auf jeden Fall mal genauer. Vor allem auch der generelle Aufbau der Seite mit PHP ist wirklich spannend. Ich habe oft das Gefühl, ich kriege deutlich mehr Einsicht, wenn ich mir richtige Projekte von anderen Menschen anschaue.

            Wenn es dir nicht behagt, wiederholt $_GET['subpage'] zu verwenden und damit immer wieder dasselbe zu tun, kannst du das auch mit einer Schleife machen;

            Ja, ist eine Überlegung wert. Allerdings versuche ich das wohl tatsächlich eher mit aria-current zu lösen, ist wohl sauberer.

            1. @@borisbaer

              Wenn es dir nicht behagt, wiederholt $_GET['subpage'] zu verwenden und damit immer wieder dasselbe zu tun, kannst du das auch mit einer Schleife machen;

              Ja, ist eine Überlegung wert. Allerdings versuche ich das wohl tatsächlich eher mit aria-current zu lösen, ist wohl sauberer.

              Das Eine hat mit dem Anderen nichts zu tun.

              Du kannst entweder eine Schleife über ein vorher definiertes Array machen oder if … elseif … elseif … else … (oder switch { case … case … default …}).

              Bei beiden Varianten setzt du für den jeweils aktuellen Menüpunkt href="#main (o.ä.) und aria-current="page"; für alle anderen Menüpunkte href` auf die jeweilige Unterseite.

              🖖 Живіть довго і процвітайте

              --
              When the power of love overcomes the love of power the world will know peace.
              — Jimi Hendrix
  2. Hallo borisbaer,

    du kannst die Generierung der "current page" Markierung in eine Funktion auslagern. So zum Beispiel:

       function mark_current_subpage($subpage_id) {
          return (($_GET['subpage'] ?? 'game') == $subpage_id)
              ?  "aria-current='page'"
              : "";
       }
       
       function game()
       {
    ?>
    	<a <?= mark_current_subpage('game') ?> href="game">Spiel</a>
    <?php 
       }
    

    Das ?? ist der "Null-Koaleszenz" Operator (null coalescing). Er liefert einen Defaultwert, wenn ein Wert NULL ist oder ein Array-Eintrag nicht existiert. Ich habe 'game' als Defaultwert gesetzt, weil das bei Dir die Defaultseite zu sein scheint, wenn als subpage nichts geliefert wird.

    Auf diese Weise lässt sich die Generierung der current-page Markierung auf einen Einzeiler reduzieren. Ob Du nun Gunnars Vorschlag folgst und aria-current='page' setzt oder bei class='current' bleibst, ist deine Entscheidung.

    Im CSS kannst Du das aria-current Attribut so prüfen:

    a[aria-current=page] {
       ...
    }
    

    Statt in einer Funktion jedesmal neu zu ermitteln, ob subpage gesetzt ist, könntest Du das auch einmal tun, an der Stelle, wo Du das Menü generierst. Ob das sinnvoll ist, hängt davon ab, ob das nur eine einzige Stelle ist oder ob sich das durch den Code verteilt.

    Aber wenn's nur eine Stelle ist, kannst Du den Namen der angeforderten Subpage einmal ermitteln und dann den Funktionen, die den Link erzeugen, als Parameter übergeben.

    ODER Du machst eine Klasse, die die Menü-Methoden enthält, und setzt dort den Namen der aktuellen Seite als Eigenschaft ein.

    Du hast die Wahl der Qual.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. @@Rolf B

      Ob Du nun Gunnars Vorschlag folgst und aria-current='page' setzt oder bei class='current' bleibst, ist deine Entscheidung.

      Warum erweckst du hier den Eindruck, die Hervorhebung der aktuellen Seite im Menü sei nur für sehende Nutzer von Belang und könnte anderen Nutzern vorenthalten werden? 😠

      🖖 Живіть довго і процвітайте

      --
      When the power of love overcomes the love of power the world will know peace.
      — Jimi Hendrix