Bernhard Häussner
Tags: Artikel mit dem Tag «PHP» durchstöbern

Howto: PHP5.3 parallel zu PHP5 installieren

27.10.2009, 17:49

Wenn man die neuen PHP-5.3-Funktionen testen will, aber trotzdem für die meisten Projekte aus Kompatibilitätsgründen eine ältere PHP(5)-Version benutzen will, kann man PHP 5.3 parallel als CGI-Modul installieren. Dazu sind folgende Schritte möglich:

Zunächst benötigt man den Quelltext (Sourcecode) der aktuellen php5.3-stable Version. Er ist zu finden auf der PHP-Download-Seite bzw. Version 5.3.0 kann so direkt heruntergeladen werden:

wget http://de2.php.net/get/php-5.3.0.tar.gz/from/this/mirror
tar xzfv php-5.3.0.tar.gz
cd php-5.3.0/

Dann muss die PHP-Installation konfiguriert werden. Dazu benutzt man zunächst in der Ausgabe von phpinfo() das "Configure Command" als Basis. In diesem ändert man dann in den Dateipfaden php5 zu php53 und man setzt --bindir=/usr/bin/php53, um Konflikte mit anderen php5-Binaries zu vermeiden. Das sieht z.B. so aus:

./configure --prefix=/usr/share/php53\
 --datadir=/usr/share/php53\
 --mandir=/usr/share/man\
 --bindir=/usr/bin/php53\
 --with-libdir=lib64\
 --includedir=/usr/include\
 --sysconfdir=/etc/php53/apache2\
 --with-config-file-path=/etc/php53/apache2\
 --with-config-file-scan-dir=/etc/php53/conf.d\
 --enable-libxml\
 --enable-session\
 --with-pcre-regex=/usr\
 --enable-xml\
 --enable-simplexml\
 --enable-filter\
 --disable-debug\
 --enable-inline-optimization\
 --disable-rpath\
 --disable-static\
 --enable-shared\
 --with-pic\
 --with-gnu-ld

Eine Liste mit allen Optionen zeigt ./configure --help. Das Konfigurieren überprüft alle Einstellungen und wird eine Weile laufen - und meistens nicht beim ersten Mal klappen, was meistens bedeutet, dass --with-xyz verwendet wurde, ohne dass die entsprechenden Libraries gefunden wurden. Dann muss man das Modul deaktivieren oder nach einer Lösung googeln/bingen. Manchmal fehlen auch Header, welche, je nach Distribution, in einem Paket ähnlich wie [lib]xyz-dev[el] zu finden sind.

Als nächstes wird das ganze kompiliert und (als root/superuser) die neue PHP-Version installiert:

make
# optional, dauert, findet aber Fehler:
make test
# bringt die Dateien an ihren Platz: 
sudo make install

Jetzt muss noch Apache über das neue CGI-Modul informiert werden. Dazu müssen die folgenden Konfigurationen gesetzt werden (z.B. elegant in einer neuen Datei /etc/apache2/conf.d/php53cgi.conf):

# Damit mod_mime die neue Dateierweiterung erkennt:
AddHandler php53-cgi .php53
# Damit die Binaries aufgerufen, nicht versendet, werden:
ScriptAlias /bin-php53 /usr/bin/php53/
# Was eigentlich passieren soll:
Action php53-cgi /bin-php53/php-cgi
# Und noch ein paar Einstellungen für die Binaries:
<Directory /usr/bin/php53>
    # Kein .htaccess:
    AllowOverride None
    # CGI aktivieren, softlinks folgen
    Options +ExecCGI +FollowSymLinks
    # normalerweise ist der Zugriff auf alle „sonstigen“ Dateien gesperrt, 
    # daher hier wieder freigeben: 
    Order allow,deny
    Allow from all
</Directory>

Zum Testen kann man z.B. eine phpinfo.php53-Datei anlegen und man sollte die neue PHP-Verion angezeigt bekommen. Auf dieser Info-Seite kann man dann prüfen, ob alle Module verwendbar sind, die Zeitzone gesetzt ist und anderen potentielle Konfigurationsfehler auf die Schliche kommen. PHP 5.3 wird auch seine eigene php.ini in /etc/php53/apache2/php.ini verwenden, wohin man vielleicht zunächst die alte Konfigurationsdatei kopieren will.

# php.ini replazieren:
sudo cp /etc/php5/apache2/php.ini /etc/php53/apache2/php.ini
# im Webroot phpinfo mit namespace-Spielerei anlegen :)
echo "<?php namespace YEEES; phpinfo();" > phpinfo.php53

Nach den Schritten dieses Tutorials dürfte dann PHP 5.3 mehr oder weniger optimal laufen, ohne dass die Funktion alter Scripte eingeschränkt werden muss, und es lässt sich einfach die Interkompatibilität zwischen den Versionen testen.

Keine Garantie, Verwenden auf eigenes Risiko.

Edit 2009-10-28: On-the-fly zu PHP5.3 Wechseln

Das ständige Umbenennen von Dateien kann natürlich sehr mühsam sein. Leicht wechselt man ganze Verzeichnisse zu PHP5.3 indem man folgendes in ihrer .htaccess-Datei einfügt:

AddHandler php53-cgi .php

So kann man dann auch zum Testen schnell den ganzen Server zwischen den Versionen wechseln.

Kommentare: 7 Einträge

My MySQL API onion for PHP

26.07.2009, 17:42
MySQL Onion

MySQL Onion

A huge part of web applications is usually the interaction with the SQL database. This is why I want as little work as possible connecting, escaping values, getting the right tables an so on in PHP. But it should stay simple and allow modular approaches. Therefor I'm using some nested APIs for doing queries easily:

PDO

The very fist thing I am using is PDO. It can handle many RDBMS, but I am most of the times using MySQL or SQLite. By using PDO as an API for the following layers I can make sure most of the code will work for many RDBMSs. PDO even simplifies transactions and prepared statements. Here's some sample PHP code using PDO:

$pdo=new PDO('mysql:host='.$host.';dbname='.$db, $user, $passwort);

$pdo->exec('UPDATE test SET foo="bar" WHERE id=4');

$satement=$pdo->prepare('SELECT * FROM blogeintraege WHERE id=:id');
$satement->bindValue(':id',3,PDO::PARAM_INT);
$satement->execute();
$data=$satement->fetchAll();

PDO Simplifier

The next layer is a class that will hold a MySQL database Connection (a PDO Object) and offer some simple functions for doing e.g. a simple prepared statement. Instead of binding each values manually, you can throw an array in.

It also includes a cache, if you want to run statements more than once. It can append a prefix to all queried tables and checks dynamically inserted tables for validity to avoid SQL-injections and MySQL errors. It is used like that:

$res=$db->sql("SELECT * FROM blogeintraege");
$res=$db->sql(
  "SELECT * FROM #test WHERE id=:id",
  array('id'=>$id),array('id'=>PDO::PARAM_INT),
  array('test'=>'blogeintraege'),
  array('limits'=>array(0,$l),'buffered'=>false)
);

For one array element this does not look too tiny, but the more values are bound, the more useful it gets. And it is very useful if you already have your values in an array, like $_GET.

Note that nearly everything is optional. The table array can contain more tables, for example you can have an array of tables for different languages, if they are in different tables. The bind-types don't need to be specified too. You can even leave out everything except the query as shown in the fist line of code. The Result will by default be returned as a nice array (the GROUP_CONCAT fields are array'ed too) but you can use all other PDO fetch types.

This layer follows a rather functional approve, so I needed another layer for accessing the central sql()-Function in an OOP manner. This should avoid some runtime errors and you can modify the SQL in a modular system.

Statement builder

So I created a wrapper object, that holds a pointer to the database and will construct the parameters for sql(). This comes in handy as more and more optional parameters are added.

The PDO Simplifier has a method to build such statement-objects called sqlO(). This is how the wrapper is used:

$db->sqlO('INSERT INTO blogtaglinks SET ##,type=3')
   ->setSet(array('ID_tag'=>$lasttagid,'ID_entry'=>$id))
   ->exec();
$res=$db->sqlO("SELECT * FROM #test WHERE id=:id")
        ->setData(array('id'=>$id))
        ->setDataTypes(array('id'=>PDO::PARAM_INT))
        ->setTables('test'=>'blogeintraege'),
        ->setLimits(0,$l)
        ->setBuffered(false)
        ->exec();
);

As you can see, it is a little more code, but the code is pretty self-explanatory and now one can build the sets and the other parameters as arrays and then include them easily in the statements.

A bit different: Zend Framework's $db->select() approach

A next step would be to build queries with a single API. This is a feature implemented by the Zend Framework, where you can build your SQL with some API functions and it will even work across various databases:

select = $db->select()
  ->from('blogeintraege',array('id','Titel'))
  ->where('id < ?', $id)
  ->order('id DESC')
  ->limit(0,10);

Well doesn't that look nice?

Kommentare: 1 Einträge

MySQL-Queries sparen mit GROUP_CONCAT

22.06.2009, 20:10
Ein MySQL-Query mit GROUP_CONCAT

Ein MySQL-Query mit GROUP_CONCAT

Fast jede (Web-)Anwendung, die eine Datenbank wie MySQL benutzt, muss irgendwie das Problem lösen, zu einem Artikel mehrere Tags, zu einem Eintrag mehrere Kommentare, zu einem Wort mehrere Synonyme zu finden, die sich in einer weiteren Tabelle befinden. Jedes Mal, wenn mehrere Zeilen Abgefragt werden sollten, die wieder mehrere untergeordnete Zeilen haben, bedeutet das meistens für jedes Element einen weiteren Query zu starten, oder eine zweite Abfrage mit einer weiteren Liste zu starten, oder eine riesige Reihe Datensätze abzufragen und der Anwendung das Gruppieren zu überlassen. Mit GROUP_CONCAT (Dokumentation) lässt sich in MySQL (fast) alles in ein SQL-Query zusammenfassen.

Einführung

Nehmen wir eine einfache Datenbankstuktur an, wie die eines Blogs. Es gibt blogeintraege mit einem Titel, blogtags mit einem Namen. Dann gibt es noch eine Tabelle, die beides verbindet, z.B. blogtaglinks. Jeder Datensatz hat natürlich eine eindeutige ID. Wenn man jetzt alle Blogeinträge auflisten will, könnte man das wie folgt machen:

SELECT Titel FROM blogeintraege

Um die Anzahl der Verbundenen Tags herauszufinden, wäre ein solcher Join üblich:

[pr:sqle]SELECT Titel, COUNT(distinct(t.ID)) FROM blogeintraege b, blogtags t, blogtaglinks l WHERE b.ID = l.ID_entry and t.ID = l.ID_tag GROUP BY b.ID[/pre]
+-------------------------+-----------------------+
| Titel                   | COUNT(distinct(t.ID)) |
+-------------------------+-----------------------+
| Hello World!            |                     2 |
| The CMS                 |                     2 |
| To be Released Later... |                     1 |
+-------------------------+-----------------------+
3 rows in set (0.01 sec)

Hier werden jeweils alle Spalten, die den selben Blogeintrag repräsentieren gruppiert und dann aus diesen Gruppen die Anzahl der verschiedenen Tags abgefragt. Dabei ist COUNT die Gruppenfunktion, die die nun versteckten, „weg-gruppierten“, Datensätze verarbeitet - hier zählt.

Die GROUP_CONCAT-Funktion

MySQL und andere RDBMSs kennen für Zahlen einige Funktionen, die Summe, Durchschnitt etc. gruppierter Zahlen berechnen können. MySQL kennt darüber hinaus die GROUP_CONCAT()-Funktion, welche die „versteckten“ Werte (also auch CHAR) in einer Spalte zusammen zu hängen. Mit dieser Funktion wäre es also möglich, die Tags direkt aufzulisten:

SELECT Titel, GROUP_CONCAT(distinct(t.name))
FROM blogeintraege b, blogtags t, blogtaglinks l
WHERE b.ID = l.ID_entry and t.ID = l.ID_tag
GROUP BY b.ID
+-------------------------+---------------------------------+
| Titel                   | GROUP_CONCAT(distinct(t.name))  |
+-------------------------+---------------------------------+
| Hello World!            | Projekt: Mein Blog,Persönliches |
| The CMS                 | PHP,Projekt: Mein Blog          |
| To be Released Later... | Projekt: Mein Blog              |
+-------------------------+---------------------------------+
3 rows in set (0.00 sec)

Jetzt wird anstatt der Anzahl der Tags eine Komma-getrennte Liste der Tags angezeigt. Das ist insofern ein recht großer Fortschritt, da wir nicht nochmal eine zweite Abfrage machen müssen, die uns jeweils die Namen der Tags heraus sucht.

Weiter Ausbauen

Jetzt wird es Zeit ein bisschen zu Optimieren. In diesem Beispiel macht es wenig Sinn, aber theoretisch könnten zwei Tags den gleichen Namen haben. In diesem Fall würde uns MySQL aber den Namen nur einmal anzeigen, da distinct() doppelte Werte aussortiert. Wir würden also weniger Tags bekommen, als ein COUNT(distinct(t.ID)) anzeigt. Das können wir lösen, indem wir die distinct()-Funktion auf die IDs der Tags anwenden:

SELECT Titel, GROUP_CONCAT(distinct(t.ID),'-', t.name)
FROM blogeintraege b, blogtags t, blogtaglinks l
WHERE b.ID = l.ID_entry and t.ID = l.ID_tag
GROUP BY b.ID

Das würde uns für jeden Blogeintrag eine Liste erstellen, wie 42-Webbrowser,28-Productivity,27-Design. Die eigentlich überflüssigen IDs werden später noch entfernt, z.B. mit PHP.

Wenn wir zusätzlich noch die Erstellungsdaten z.B. der letzten drei Kommentare abfragen wollen, zeigt sich, warum wir distinct() verwenden müssen: Sonst wird jeder Tag so oft angezeigt, je nach dem, wie viele Kommentare ein Eintrag hat. Wenn wir also noch eine Tabelle benutzen, wie blogkommentare mit creation_time, und uns jeweils die letzten Erstellungszeiten anzeigen lassen wollen, benutzen wir:

SELECT Titel, GROUP_CONCAT(distinct(t.ID),'-', t.name), 
GROUP_CONCAT(distinct(c.ID),'-',c.creation_time ORDER BY creation_time DESC)
FROM blogeintraege b, blogtags t, blogtaglinks l, blogkommentare c
WHERE b.ID = l.ID_entry and t.ID = l.ID_tag and c.entry_id = b.id
GROUP BY b.ID

Jetzt haben wir noch eine weitere Spalte mit einem Wert, wie 34-2008-10-20 18:28:41,33-2008-10-19 23:22:37. Doch je mehr Spalten wir abfragen, desto unübersichtlicher werden die Listen. Außerdem hängt ja immer noch die ungewollte, aber doch notwendige id davor.

Verarbeitung mit PHP

Diese minderen kosmetischen Makel lassen sich dann in der Anwendung noch beheben. Hier zeige ich, wie ich mit PHP die Listen in Arrays lade und sortiere:

function arrayifyGrpConcats($q,$resultset) {
  $regexp='/\\bgroup_concat\\((.*?) separator \'(?<sep>.*?)\'\\) +as +(?<name>\w+)\\b/i';
  if ( preg_match_all($regexp,$q,$matches,PREG_SET_ORDER) ) {
    foreach ($matches as $grpCoClause) {
      if (isset($grpCoClause["name"],$grpCoClause["sep"])) {
        $name=$grpCoClause["name"];
        if (strpos($name,'_',1)===false) {
          $foldername=$name;
          $subname=false;
        } else {
          $foldername=substr($name,0,strpos($name,'_',1));
          $subname=substr($name,strpos($name,'_',1)+1);
        }
        $s=$grpCoClause["sep"];
        foreach ($resultset as $id=>$row) {
          $val=$row[$name];
          unset($resultset[$id][$name]);
          if ($subname) {
            if (!isset($resultset[$id][$foldername])) {
              $resultset[$id][$foldername]=array();
            }
            $vals=explode($s,$val);
            for ($i=0, $len=count($vals);$i<$len;$i++) {
              if (!isset($resultset[$id][$foldername][$i])) {
                $resultset[$id][$foldername][$i]=array();
              }
              $resultset[$id][$foldername][$i][$subname]=preg_replace('/\\d+-/','',$vals[$i],1);
            }
          } else {
             $resultset[$id][$foldername]=explode($s,$val);
          }
        }
      }
    }
  }
  return $resultset;
}

Diese Funktion kann direkt nach der Abfrage auf die Ergebnisse angewendet werden. Dazu übergibt man ihr das Result als durchnummeriertes Array mit je einem assoziativen Unterarray (Dictionary) pro Datensatz, der als Schlüssel die Spaltennamen und als Werte den jeweiligen Spaltenwert enthält. Die Funktion sucht dann im Query nach GROUP_CONCAT-Aufrufen der From group_concat(... separator ',') as iwas. Das Trennsymbol wird explizit angegeben, damit es hier keine Missverständnisse gibt.

Man sollte bedenken, dass MySQL mit dem Separator im Wert nichts macht. Wie man im SQL sieht habe ich das Problem mit dem Trennzeichen im Wert „gelöst“, indem ich es aus dem Kommentartext einfach gelöscht habe. Eine ausgefeiltere Methode habe ich bei stackoverflow gefunden.

Als Alias nach as kann man fast alles angeben. Die Funktion wird diesen Namen als Spaltennamen im Result-Array verwenden.

Als kleines Extra habe ich noch eingebaut, dass die Funktion optional mehrere Spalten zusammen nimmt, wenn sie ein Alias der Form gruppe_name erkennt: Sie wird dann im Resultset-Array unter gruppe ein nummeriertes Array anlegen, das ein assoziatives Arrays enthält mit den verschieden name. Wenn wir also zu einem Kommentar nicht nur das Datum, sondern auch den Autor wissen wollen, könnten wir group_by... as comm_time, group_by... as comm_name verwenden, um die Daten für jeden Kommentar zusammen zu halten.

Im Endeffekt kann das dann so aussehen:

SELECT b.id, b.Titel AS Titel,
group_concat(DISTINCT t.ID,'-', t.name separator ', ') as tags_Name, 
group_concat(DISTINCT t.id separator ', ') as tags_id, 
group_concat(DISTINCT c.ID,'-',c.creation_time separator ', ') as comm_time, 
group_concat(DISTINCT c.ID,'-',REPLACE(LEFT(c.Text,20),',','') 
     separator ', ') as comm_text 
FROM blogeintraege b, blogtaglinks l, blogtags t, blogkommentare c
WHERE b.ID = l.ID_entry and t.ID = l.ID_tag and c.entry_id=b.id
GROUP BY b.ID LIMIT 1,2

Was uns dann dieses Ergebnis liefert: (print_r)

Array
(
    [0] => Array
        (
            [id] => 2
            [Titel] => The CMS
            [tags] => Array
                (
                    [0] => Array
                        (
                            [Name] => PHP
                            [id] => 3
                        )

                    [1] => Array
                        (
                            [Name] => Projekt: Mein Blog
                            [id] => 2
                        )

                )

            [comm] => Array
                (
                    [0] => Array
                        (
                            [time] => 2008-10-19 23:22:37
                            [name] => Bernhard H.
                            [text] => Also das Kommentarsy
                        )

                    [1] => Array
                        (
                            [time] => 2008-10-20 18:28:41
                            [name] => Bernhard H.
                            [text] => So ich hab es jetz 
                        )

                )

        )

    [1] => Array
        (
            [id] => 10
            [Titel] => To be Released Later...
            [tags] => Array
                (
                    [0] => Array
                        (
                            [Name] => Projekt: Mein Blog
                            [id] => 2
                        )

                )

            [comm] => Array
                (
                    [0] => Array
                        (
                            [time] => 2008-10-20 19:48:50
                            [name] => Bernhard H.
                            [text] => Cool das klappt auc
                        )

                )

        )

)

Ich hoffe mit dieser Methode in Zukunft Datenbankabfragen zu vermindern und damit die Geschwindigkeit der Anwendungen weiter zu erhöhen. Zwei kleine Sachen noch:

SELECT @@group_concat_max_len

Dieser Wert ist die maximale Länger der mit GROUP_CONCAT gebildeten Spaltenwerte und lieget meist bei 1024. Außerdem kann es nötig sein den Spaltenwerte von BLOB zu konvertieren: CAST(... as CHAR).

Kommentare: keine

Shortlinks implementiert

22.05.2009, 20:09

Da mein Domainname doch recht lang zu tippen ist, und sich für twitter sowieso kurze URLs gut eigenen, habe ich jetzt auf dieser Seite die meisten Seiten mit kurz-URLs ausgerüstet. So kann man jetzt z.B. mit meinem ShortURL Auto-Discovery Twitter Bookmarklet ohne Probleme Links auf diese Seite posten und man muss nur noch 1-co.de in die Adressleiste tippen, um auf die Startseite zu kommen.

Die Shortlinks werden auf den entsprechenden Seiten mit dem HTML-Tag <link rel="shortlink" href="http://1-co.de/bj" /> und mit dem HTTP-Header Link: <http://1-co.de/bj>; rel=shortlink ausgewiesen.

Hauptsächlich möglich wird das durch ein paar manuelle Einträge in der .htaccess und durch diese zwei bzw. vier netten Helfer-Funktionen (PHP), die Zahlen (int) in Zeichenfolgen (Strings) umwandeln, wodurch sie rund 20-40% kürzer werden:

function toAlphaNumber($num){
  $anum = '';
  $BASE=62;
  while ($num>=1) {
    $x=$num%$BASE;
    $anum=chr2($x).$anum;
    $num=floor($num/$BASE);
  }
  return $anum;
}
function fromAlphaNumber($str){
  $BASE=62;
  $num=0;
  for ($i=0,$len=strlen($str);$i<$len;$i++) {
    $num+=uchr2($str{$i})*(pow($BASE,($len-1-$i)));
  }
  return $num;
}
function chr2($i) {
  $i+=48;
  if ($i>57) $i+=7;
  if ($i>90) $i+=6;
  if ($i>122) throw new Exception('Too high');
  $c=chr($i);
  return $c;
}
function uchr2($c){
  $i=ord($c);
  if ($i>122) throw new Exception('Too high');
  if ($i>90) $i-=6;
  if ($i>57) $i-=7;
  $i-=48;
  return $i;
}

In der .htaccess:

#manual shortening
RewriteRule ^b$ http://bernhardhaeussner.de/blog [R=301,L]
# [...]

#call a php script that redirects to the right blog post
RewriteRule ^b/([A-Za-z0-9]+)$ blogentry.php?encid=$1 [L]

Jetzt bin ich ja mal gespannt, wie viele Leute meine Posts twittern...

Für Wordpress-User (Update 2009-06-23)

Wer sich mit nichts herum ärgern will und ein ausgereiftes und einfach konfigurierbares Plugin sucht, ist mit dem Plugin Twitter Friendly Links (empfohlen von eins78) gut aufgehoben.

Für Wordpress-User & Bastler (Update 2009-06-20)

Auf Anfrage von eins78 hier noch drei Ticks für Wordpress-Blogs: Theoretisch können auch Worpress-Autoren einfach das Bookmarklet ins HTML-Markup der Seite setzten, es ist ja ein ganz normaler Link. Allerdings funktioniert es nicht ohne Javascript und es gibt folgende elegante Lösung:

Empfehlenswerter und fast genauso einfach ist es, dieses Plugin zu benutzen: Es kümmert sich nicht nur um die Erstellung von kurz-URLs und den entsprechenden Link-Tag im <head>-Bereich: Es erstellt u.a. eine globale Variable $shortlink_url in der die kurze URL gespeichert ist. Dann muss man nur noch im Template-Ordner wp-content/themes/(templatename)/ in der (index.php) folgendes in das Template setzen:

<?php if ( isset($shortlink_url) && $shortlink_url!='' ): ?>
  <a
   href="http://twitter.com/home/?status=check+out+<?php echo $shortlink_url;?>"
   rel="nofollow shortlink"
  >
  tweet this
  </a>
<?php endif; ?>

Ich habe es probeweise im default-Template direkt in das <small> gesetzt, in dem auch Datum und Autor stehen, dann wird es klein unter der Überschrift angezeigt. Die Darstellung des Links lässt sich natürlich noch anpassen. Nachteil an dem Plugin ist, dass die Weiterleitung nicht auf Anhieb funktioniert. Man müsste erst ungefähr so etwas in die .htaccess schreiben:

RewriteRule ^([0-9]+)$ ?p=$1 [R=301,L]

Eine Lösung ganz ohne Plugin hat Thomas Scholz veröffentlicht. Sie „schont die Optionstabelle und erleichtert individuelle Anpassungen“. Allerdings enthalten die Kurzlinks ?p=, weshalb die URLs nicht ganz so schön werden.

Kommentare: 7 Einträge

Wellen mit PHP rendern

30.04.2009, 19:52

Da ich heute mal wieder etwas Zeit zum Programmieren hatte (bzw. zum was-ich-will-Programmieren) habe ich unseren aktuellen Physik-Stoff visualisiert und zwar mit PHP und GD.

Ich habe ja schon vorher ein wenig mit SVG herumexperimentiert. Das war allerdings alles etwas krum und schief und ganz ohne Mathematik per Hand zusammengebastelt:


interference

Jetzt musste natürlich ein Algorithmus her. Eigentlich sind es nur 2 Klassen, die festlegen, wie eine Welle aussieht und was passiert, wenn sie von einem Zentrum ausgeht. Und zwar werden zyklische Wellenfronten von verschiedenen Zentren aus übereinandergelegt. Schwer zu erklären, einfacher im PHP-Code zu sehen:

class Wave {
  function __construct($length,$amplitude) {
    $this->length=$length;
    $this->amplitude=$amplitude;
  }
  function getPhaseAtLength($s) {
    $phase=($s%$this->length)/$this->length;
    $phaseWithDelta=fmod($phase+(isset($this->phase)?$this->phase:0)+1,2)-1;
    return $phaseWithDelta;
  }
  function getAmplitudeAtLength($s) { //sinuswelle
    $A=sin(deg2rad($this->getPhaseAtLength($s)*360))*$this->amplitude;
    return $A;
  }
  function setPhase($phase) {
    $this->phase=$phase;
    return $this;
  }
}

class Wavecenter {
  function __construct(Wave $wave,Point $center) {
    $this->wave=$wave;
    $this->center=$center;
  }
  function getAmplitudeAtPoint(Point $point) {
    $distance=$this->center->getDistance($point);
    $amp=$this->wave->getAmplitudeAtLength($distance);
    return $amp;
  }
}

Und mit ein bisschen anderem Code, der das ganze Visualisiert, entsteht dann sowas, wie die roten Wellen oben.

Das erste ist natürlich keine Sinuswelle: Am Anfang hat meine Point::getDistance() nicht funktioniert (^ statt pow()), so dass diese seltsame Figur entstanden ist. Interessant ist, dass man bereits zwei überlagerte Wellen sieht (daher die Symmetrie), diese jedoch seltsam deformiert sind.

Da ich gerade noch eine Phasenverschiebung dazu gezimmert habe, kann ich die Wellen auch noch animieren, also nicht nur in der Position des Mittelpunkts, sondern sie auch wandern lassen. Das sieht dann so aus:

Als kleinen Nachtrag noch ein paar Überlagerung einer Welle und anderen mit kleinerer Wellenlänge, wobei die Wellenlängen in (anfangs) kleinen ganzzahligen Verhältnissen stehen. Daher kann man sie auch recht leicht in der Musik finden. Die oberste Reihe wäre demnach das Schallbild, wenn man 1, 2, und 3 mal den selben Ton spielt, nur eben in der anderen Oktave. Das hört man normalerweise recht einfach, aber beim Sehen ist es etwas anders:

Jetzt wäre es natürlich irgendwie cool, auch einzelne Wellen irgendwie umher wandern zu lassen, um Doppler-Effekt und Reflexion etc. zu zeigen, aber da wären etwas kompliziertere Models nötig.

Nachtrag 26. Mai 2009: OpenGL

Da ich vor kurzem begonnen habe mit Processing Grafiken zu basteln, habe ich Processing auch gleich mal für das Wellen-Darstellen benutzt. Und das funktioniert sehr gut: Processing bietet die Grundlagen, sodass man leicht eine Welle, die von einem Zentrum ausgeht rendern kann. Das macht man dann z.B. mit 10 Frames, immer mit leichter Phasenverschiebung. Das könnte man dann einfach animieren. Wesentlich ansehnlicher ist es aber, wenn man die gerenderte Welle als animierte Textur in eine OpenGL-Szene mit additiver Überblendung einsetzt. Dann entstehen nämlich die oben gezeigten Bilder in realtime. Abgesehen vom anfänglichen Rendern und dem Laden in den Graphikspeicher der 10 2000x2000 Pixel Texturen. Screenshot:

Vor allem wenn man mehr Wellenzentren man hat, übernimmt OpenGL viele Berechnungen, weshalb man die Wellenzentren ohne Wartezeiten einfach mit der Maus verschieben kann.

Kommentare: keine
[ Seite 1 2 3 4 ] Nächste Seite »
 
Χρόνογραφ
© 2008-2014 by Bernhard Häussner - Impressum - Login
Kurz-Link zur Homepage: http://1.co.de/