tag:www.stefanwienert.net,2008:/php Php - Stefan Wienert's Blog 2010-09-03T08:40:21Z Enki Stefan Wienert stwienert@gmail.com tag:www.stefanwienert.net,2008:Post/53 2010-09-03T06:40:00Z 2010-09-03T08:40:21Z Drupal Modulentwicklung - I - Einführung und Hello World <p>Beruflich habe ich recht viel mit Drupal zu tun, einem in <span class="caps">PHP</span> geschriebenem <span class="caps">CMS</span>. Das Plugin-System, Module genannt, ist recht umfangreich und bietet vielfältige Möglichkeiten, fast jede Stelle des CMS&#8217; anzupassen, ohne wirklich am Kerncode rumzuhacken.</p> <p>Das ganze ist recht durchdacht, und bei weitem besser als nacktes <span class="caps">PHP</span>. Desweiteren bin ich kein großer &#8220;Fan&#8221; von den beiden Modulen &#8220;Views&#8221; und &#8220;<span class="caps">CCK</span>&#8221;, die sehr oft verwendet werden, um Modelle und <span class="caps">SQL</span> Statements nachzubilden. Ich bevorzuge aber Code, und nicht das Zusammenklicken dessen.</p> <p>Voraussetzung für das Tutorial:</p> <ul> <li>installiertes Drupal 6</li> <li><span class="caps">PHP</span>/<span class="caps">SQL</span> Erfahrungen</li> </ul> <h3>Hello World Modul</h3> <p>Drupal bietet zwei Ordner an, in die wir unsere Module packen können: ./modules und ./sites/all/modules. Der Konvention nach, sollten selbstgeschriebene Module in den letzteren, beides arbeitet aber korrekt.</p> <p>In einen der beiden Ordner legen wir nun einen neuen Ordner an:</p> <p>./modules/helloworld bzw. ./sites/all/modules/helloworld</p> <p>In der Regel heißt der Ordner des Moduls so, wie das Modul selbst. Das muss aber nicht so sein. Auch können noch beliebige weitere Unterordner vorher folgen, um z.B. alle Module einer gewissen Funktionalität zu bündeln.</p> <p>Ein Drupalmodul hat mindestens 2 &#8220;magische&#8221; Dateien, also Dateien, die per Definition eine besondere Rolle haben.</p> <p>./helloworld/helloworld.info<br /> ./helloworld/helloworld.module</p> <h4>helloworld.info</h4> <p>./helloworld/helloworld.info ist nur eine Datei, wo Metainformationen über das Modul stehen, und woraus die Informationen für die Modulseite entnommen werden. Der Aufbau ist vorgegeben und recht kurz:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">; $Id$<tt> </tt>name = Hello World Modul<tt> </tt>description = ...<tt> </tt>package = StWienert<tt> </tt>version = 1.0<tt> </tt>core = 6.x<tt> </tt>dependencies = user<tt> </tt></pre></td> </tr></table> <p>Damit definiere ich, dass mein Modul vom Modul user abhängt, also nicht ohne dieses aktiviert werden kann, und in der &#8220;Package&#8221; StWienert, d.h. einer gesonderten Gruppierung &#8220;StWienert&#8221;, in der Modulübersicht zu finden ist.</p> <h4>helloworld.module</h4> <p>Dies ist eine <span class="caps">PHP</span>-Datei, die erst aufgerufen wird, falls unser Modul aktiviert ist. Hier der Code für ein Hello world:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="idl">&lt;?php</span><tt> </tt><span class="r">function</span> <span class="fu">helloworld_menu</span>() {<tt> </tt> <span class="lv">$items</span> = <span class="pd">array</span>();<tt> </tt> <span class="lv">$items</span>[<span class="s"><span class="dl">'</span><span class="k">test/helloworld</span><span class="dl">'</span></span>] = <span class="pd">array</span>(<tt> </tt> <span class="s"><span class="dl">'</span><span class="k">title</span><span class="dl">'</span></span> =&gt; <span class="s"><span class="dl">'</span><span class="k">Hello-World Site</span><span class="dl">'</span></span>,<tt> </tt> <span class="s"><span class="dl">'</span><span class="k">page callback</span><span class="dl">'</span></span> =&gt; <span class="s"><span class="dl">'</span><span class="k">_helloworld_page</span><span class="dl">'</span></span>,<tt> </tt> <span class="s"><span class="dl">'</span><span class="k">access arguments</span><span class="dl">'</span></span> =&gt; <span class="pd">array</span>(<span class="s"><span class="dl">'</span><span class="k">access content</span><span class="dl">'</span></span>),<tt> </tt> <span class="s"><span class="dl">'</span><span class="k">type</span><span class="dl">'</span></span> =&gt; <span class="co">MENU_CALLBACK</span>,<tt> </tt> );<tt> </tt> <span class="r">return</span> <span class="lv">$items</span>;<tt> </tt>}<tt> </tt><tt> </tt><span class="r">function</span> <span class="fu">_helloworld_page</span>() {<tt> </tt> <span class="r">return</span> <span class="s"><span class="dl">&quot;</span><span class="k">hello World</span><span class="dl">&quot;</span></span>;<tt> </tt>}<tt> </tt><span class="c"># kein schlie?endes </span><span class="idl">?&gt;</span> notwendig, da die Datei inkludiert wird<tt> </tt></pre></td> </tr></table> <p>Nun können wird das Modul aktivieren, und die Seite &#8220;example.com/test/helloworld&#8221; aufrufen, und sollten unser Hello World sehen.</p> <p>Drupal basiert auf einem <b>Callbacksystem</b>. Funktionen, mit einem genau spezifizierten Namen, werden zu bestimmten Zeitpunkten aufgerufen und können Einfluss nehmen. Diese besonderen Funktionen werden <b>&#8220;Hooks&#8221;</b> genannt, <a href="http://api.drupal.org/api/group/hooks/6">wovon es eine ganze Menge gibt.</a> <br /> Hier verwenden wir den sogenannten <a href="http://api.drupal.org/api/function/hook_menu">hook_menu</a>. Anstelle von &#8220;hook&#8221; müssen wir den (internen) Namen des Moduls einsetzen, also denselben Namen, den unsere Dateien .info und .module führen.</p> <p>Ein paar der interessanteren Hook-Funktionen werde ich in einem folgenden Blogpost vorstellen, ansonsten ist ein wenig Stöbern in der Übersicht angebracht.</p> <h3>Addendum</h3> <h4>Must-have-Module</h4> <p>Hier noch die wesentlichen Basis-Developer-Module, die ich nicht mehr missen möchte. Was hätte ich an Zeit gespart, hätte ich gleich das alles gewusst :)</p> <ul> <li><a href="http://drupal.org/project/admin_menu">admin_menu</a> Fügt ein JS Menü am oberen Bildrand, mit denen man sehr schnell an den benötigten Funktionen ist, und sich auf Dauer Fantastillarden an Klicks erspart. Ausserdem gibt es einen &#8220;Cache leeren&#8221; Button unter dem ersten Menüpunkt. Dieser ist wichtig, um z.B. den hook_menu unseres Moduls nach einer Änderung noch einmal aufgerufen wird. Sonst wird u.a. die Menüinformation gecacht.</li> <li><a href="http://drupal.org/project/devel">devel</a> Fügt zwei für mich wesentliche Dinge ein; eine Funktion &#8220;dpm($variable)&#8221;, die wie das print_r von <span class="caps">PHP</span> funktioniert, aber das ganze wesentlich übersichtlicher. Zweiteres einen Error-Backtrace. Also statt &#8220;Whitescreen of Death&#8221; gibt es detaillierte Fehlerinformationen, neben einer Fehlermeldung und exakter Ort des Auftretens auch alle Variablen des aktuellen Kontextes. Quasi ein Debugger für Arme. Das Modul hat noch viele Features mehr, ein Blick lohnt.</li> <li>drush Drupal-Shell. Dies ist kein Drupalmodul, sondern ein externes Programm, das eine neue Schnittstelle zu Drupal eröffnet, nämlich die Kommandozeile. Dies kann natürlich nur installiert werden, wenn man Shellzugriff auf seinen Server hat. Hier ein paar Leckerbissen:</li> </ul><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">drush dl admin_menu &amp;&amp; drush en admin_menu<tt> </tt><span class="c"># Ladet das Modul admin_menu herunter, entpackt es </span><tt> </tt><span class="c"># nach sites/all/modules, dann wird das gleich aktiviert (enabled)</span><tt> </tt>drush cc<tt> </tt><span class="c"># leert den Cache</span><tt> </tt>drush eval <span class="s"><span class="dl">&quot;</span><span class="k">echo 'hello world'</span><span class="dl">&quot;</span></span><tt> </tt><span class="c"># Führt beliebigen PHP Code im Kontext des Drupals aus</span><tt> </tt></pre></td> </tr></table> <h4><span class="caps">CRE</span></h4> <p>Der Chaosradio-Radio-Express Podcast hat eine Sendung zum Thema Drupal. Hierbei geht es aber nicht um die Modulentwicklung, sondern um das Ökosystem allgemein.<br /> Interessant ist das Fazit von Tim Pritlove: &#8220;Es ist ja nicht so, dass es [Drupal a.d.R.] sich anbiedern würde&#8221;. Ja das stimmt, Drupal haut nicht vom Hocker, und gerade die Features der z.B. DatenbankAPI nicht gerade auf Höhe der Zeit. Aber es ist was grundsolides und sehr flexibles.</p> tag:www.stefanwienert.net,2008:Post/33 2010-02-08T11:06:00Z 2010-02-08T12:06:13Z Bessere PHP Shell, ähnlich wie Rubys irb <p>Nachdem ich bei Ruby von der recht mächtigen und gut tunebaren Interactive Ruby Shell (<a href="http://tagaholic.me/2009/03/13/hirb-irb-on-the-good-stuff.html"><span class="caps">HIRB</span></a> und <a href="http://www.rubyinside.com/wirble-tab-completion-and-syntax-coloring-for-irb-336.html">Wirble</a> seien hier anzumerken) verwöhnt wurde, war ich auf der Suche nach einer ähnlichen Lösung für <span class="caps">PHP</span>.<br /> Leider ist die eingebaute <span class="caps">CLI</span> Shell mit &#8220;php -a&#8221; relativ nutzlos, da man Output selbst ausgeben muss, es keine Autocomplete gibt usw.</p> <p>Allerdings gibt es die <a href="http://jan.kneschke.de/projects/php-shell/">php-shell</a> welche recht interessant wirkt und ich heute mal runtergeladen habe.</p> <p>Unter Ubuntu muss man noch php pear nachinstallieren:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">sudo apt-get install php-pear php5-cli php5-dev<tt> </tt>sudo pear install http://jan.kneschke.de/assets/2007/2/17/PHP_Shell-0.3.1.tgz<tt> </tt><tt> </tt># Jetzt kann die shell mit:<tt> </tt>php-shell.sh<tt> </tt>#gestartet werden<tt> </tt></pre></td> </tr></table> <p>Das reduziert den <span class="caps">PHP</span>-Schmerz schonmal ein ganzes Stück :)</p> tag:www.stefanwienert.net,2008:Post/19 2009-12-16T21:37:00Z 2009-12-16T22:37:31Z PHP - Die beste Wahl für's Web? <p>Eines vorneweg: <span class="caps">PHP</span> war mein Einstieg in die Webprogrammierung. Die ersten Schritte haben, aufgrund der C-ähnlichen und mir damit bekannten Syntax, sogar sehr Spaß gemacht.</p> <p>Nicht ganz ausschweigen sollte man den Fakt, dass eine Skriptsprache ganz gleich welcher Rubrik natürlich nicht das Allheilmittel jeglicher Softwareprojekte ist, weshalb ich mich hier ganz klar auf Sprachen für (private und kleinkommerzielle) Webentwicklung beschränken möchte.</p> <h3>Story</h3> <p>Wenn man wie ich von <span class="caps">PHP</span> kommt, ist bei Ruby und Rails vieles am Anfang ungewöhnlich, und es braucht seine Zeit, bis man sich an den Fakt, das <b>alles</b> ein Objekt ist, gewöhnen muss. Dies schafft aber auch erstklassige Möglichkeiten, Programmtext effizient und damit übersichtlich zu gestalten.</p> <p>Im Gegensatz zu <span class="caps">PHP</span> wo ich bei trivialsten Dingen jedesmal das Rad neu erfinden muss (Ich denke nur an DateTime Handling, fortgeschrittene Stringfunktionen) hat Rails und Ruby eine sehr ausdrucksstarke Bibliothek dabei.<br /> Schön finde ich <a href="http://www.feld.com/wp/archives/2006/10/php-vs-ruby.html">diese Grafiken</a>. Hiernach wurden für <span class="caps">PHP</span>-Projekte deutlich mehr Line of Codes am Gesamtkuchen gezählt, die meiste Zunahme an Projekten überhaupt gegeben hat. Also man für eigentlich alle Projekte unter <span class="caps">PHP</span> eine Wagenladung Code mitbringen muss. <acronym title="Don't repeat yourself -- ALLES was man wiederverwenden könnte, wird ausgelagert"><span class="caps">DRY</span></acronym> <span class="caps">FTW</span>.<br /> Auch die extrem bunte Bibliothek von <span class="caps">PHP</span>, die scheinbar über die Jahre aus C-Sprachen zusammengeklaut wurde, mit total uneinheitlichen Verhalten von Funktionen: usort verändert sein Argument und gibt nichts zurück, str_replace belässt es und gibt das Ergebnis zurück.</p> <h3>Die Syntax &#8212; Showdown</h3> <p>Der Fakt der C-Abstammung ist für mich einer der größten Kritikpunkte, denn intuitiv ist was anderes (strstr zum Finden eines Strings&#8230;, bei Ruby: find).<br /> Wenn ich mal wieder ein etwas größeres Modul in Drupal beginn, vergesse ich so manches mal ein &#8220;;&#8221;. <br /> Das nervt und ist absolut unnötigt. Mehr als eine Anweisung ist nur für Obfascuation-Wettbewerbe geeignet. Desweiteren ist das &#8220;;&#8221; trotzdem unter Ruby <b>optional</b> nutzbar.</p> <p>Warum diese extreme Anlehnung der Syntax an den C-Sprachen? Ist sie für betriebssystemnahe Anwendungsfälle recht angebracht, da minimal abstrakt, führt sie beim Anwendungsfallszenario &#8220;Webentwicklung&#8221; doch mehr Hürden denn Erleichterungen.</p> <p>Warum wie ein Computer denken? Wenn ich doch wie ein Mensch denken kann? Für ein kleines µ mehr an Performance? Dafür mit einer deutlich angestiegenen Fehleranfälligkeit, teilweise unüberschaubaren Programmfluss und einer grottigen Wartbarkeit?</p> <p>Klar ist es toll, wenn man vorher C, C++, Java oder was auch immer programmiert hat, kurz mal <span class="caps">PHP</span> sieht, und denkt &#8220;Hey, das ist ja genau das gleiche!&#8221;. Auch ich hatte diesen Aha-Effekt (Im Rückblick nichts, gegen den Aha-Effekt, den ich z.B. bei der ersten Nutzung der <acronym title="Eine Live-Shell mit Autocomplete im Kontext der Anwendung. Das heißt, man kann dort schön seine Abfragen ausprobieren, neue Nutzer anlegen usw.">script/console</acronym>, dem ersten <acronym title="Automatisches Erstellen von Formularen, DB-Tabellen und Programmlogik">scaffold</acronym> oder der Validate &lt;&#8212;&gt; Formularfehlermeldung hatte :D)</p> <p><span class="caps">PHP</span> ist in meinen Augen als Websprache absolut ungeeignet.</p> <p>Viele gute <span class="caps">PHP</span>-Frameworks wie CakePHP, Zend oder auch auch in erweiterter Bedeutung Drupal, können meiner Ansicht nach nicht über diesen Aspekt hinwegtäuschen.</p> <p>Hier ein paar Auszüge meiner Lieblingshassobjekte:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">htmlspecialchars &lt;---&gt; h<tt> </tt>$this-&gt; &lt;---&gt; Warum muss man diese <tt> </tt>Redundanz innerhalb einer Klasse mit sich herumschleppen? <tt> </tt>Warum &quot;weiß&quot; die Klasse nicht direkt über ihre Methoden?<tt> </tt></pre></td> </tr></table> <p>Weitere Vorteile ergeben sich aus der kompletten Objektorientierung:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }">str_replace(&quot;a&quot;,&quot;b&quot;,&quot;Hallo&quot;); &lt;---&gt; &quot;Hallo&quot;.gsub(&quot;a&quot;,&quot;b&quot;)<tt> </tt>preg_match(&quot;/../&quot;,$source,$target); &lt;---&gt; target = /.../.match(source)<tt> </tt></pre></td> </tr></table> <p>Im Endeffekt ist <span class="caps">PHP</span> sowas von laberlastig und ausdrucksschwach, dass ich z.B. bei der Entwicklung von Drupal-Modulen ohne Snippets überhaupt nicht mehr klarkommen würde. Klar liegt es vielleicht auch ein stückweit an Drupal, nichsdestotrotz bietet die aufgepfropfte Objektorientierung auch hier keine Erleichtung.</p> <blockquote> <p>But it&#8217;s a complex language that contains a lot of advanced idioms which will be very hard for <span class="caps">PHP</span> and Visual Basic programmers to absorb.</p> </blockquote> <blockquote> <p>Sometimes, too much magic is too much magic, and it can definitely be the case that the flow of code is too direct or too clever to be understandable by regular developers. Developers were able to do the jump from imperative to object-oriented programming, but it was a hard fight.</p> </blockquote> <a href="http://beust.com/weblog/archives/000382.html">Quelle</a> <p>Die vielerorts kritisierte scheinbar komfortable Konvertierung von <span class="caps">PHP</span> hat auch bei mir schon zu so manchen erstaunlichen Programmverhalten geführt.</p> <p>Durch Rails habe ich aber auch die korrekte Benennung meiner Modelle und Datenbanktabellen gelernt. Also wenn ich eine Nutzer-Tabelle habe, dann heißt sie &#8220;users&#8221;, der Primärschlüssel &#8220;id&#8221;, ein Nutzerfremdschlüssel einer anderen Tabelle würde &#8220;user_id&#8221; heißen.<br /> Warum? Bei Rails wird man für die korrekte Benennung &#8220;belohnt&#8221;, indem einige Dinge komplett automatisch passieren, sollte man sich an die Konventionen gehalten haben. Dazu zählt: Benutzung von &#8220;partials&#8221; (Also eines View-Code-Schnipsels, dass für jedes Item eines Objekts einmal ausgeführt wird),</p> <p>Manch einer mag anführen, dass Syntax Geschmackssache ist, und bei der Bewertung einer Sprache nur einen Teilaspekt darstellen sollte. Dem stimme absolut nicht zu. Je mehr Sonderzeichen ( &#8220;{ } ; $&#8221;) im Textfluss sind, desto schwerer lässt es sich lesen.</p> <h3>Rails Fanboy for life?</h3> <p>Auch bei Ruby oder Rails ist (natürlich wie immer in meinen Augen) nicht alles perfekt: neidisch blicke ich auf die Einrücksemantik von Python, spart sie mir doch die Blockschlüsselwörter. Oder das von Haus aus installierte &#8220;erb&#8221; finde ich durch &#8220;haml&#8221; erst erträglich gemacht :).</p> <p>Auch habe ich ständig das Gefühl, dass ich erst einen Bruchteil der Sprache voll ausschöpfe. Insbesondere die funktionalen Aspekte wie procs habe ich bisher nur selten verwendet.<br /> Auch die sehr mächtigen Testmöglichkeiten durch gems wie rspec, cucumber oder silenium habe ich bisher eher akademisch benutzt.</p> <p>In vielen Foren habe ich auch von einem fast schon spürbaren Hass auf Rails-Nutzer bemerkt; da wird von &#8220;Latte-Machiatto schlürfende&#8221; &#8220;Fanboys&#8221; geredet, die nur &#8220;oberflächliche&#8221; Anwendungen programmieren [können]. Kein Vergleich mit JavaEE, C, whatsoever. Das mag sein, dass ein Railsprojekt nicht unbedingt als System für eine Forbes 100 Firma geeignet ist, aber für sehr viele Anwendungsfälle, insbesondere bei überschaubaren Websites, mit nicht ganz 100% mainstreammäßigen also individuell Anforderungen (&#8220;Blog&#8221;, also Wordpress, Mini social Community Drupal, usw) ist es in vielerlei Hinsicht eine bessere Wahl als&#8230; <span class="caps">PHP</span>. <br /> Schön gesagt von xaviershay auf github:</p> <blockquote> <p>No ugly <span class="caps">PHP</span> Stylings burning your eyeballs.</p> </blockquote> <a href="http://github.com/xaviershay/enki">Quelle</a> <p>Ganz ausgelassen habe ich hierbei Django/Python, dass ganz oben auf meiner Liste der zu begutachtenden Frameworks steht.</p> <h3>Fazit?</h3> <p>Ja ich bin subjektiv. <br /> Vielleicht liegt es an dem gut gemachten Screencast von Ryan Bates und teach-me-code? Oder der per Plugins (wirble, hirb) aufgemöbelten Liveshell irb bzw. script/generate, die mir so manchen &#8220;Ausprobier&#8221;-Lauf erspart hat (Kein Vergleich mit php -a )? Oder an dem teils humorig geschrieben Buch von O&#8217;Reilly? Oder an der Wahl der Namen? (&#8220;god&#8221; das Monitoring Tool, &#8220;sass:Syntactically awesome Stylesheets&#8221;, &#8220;<span class="caps">YAML</span>&#8221;: Yet another markup language&quot;, hpricot also wie Aprikose&#8230;)? <br /> <b>Ruby/Rails verbreitet einfach eine andere Grundstimmung</b> &#8211; Die Leute haben Spaß beim Programmieren!</p> tag:www.stefanwienert.net,2008:Post/5 2009-08-18T08:47:00Z 2009-10-18T10:47:18Z 3 Links zum Thema Rails <p>Einen interessanten Artikel aus dem t3n-Magazin zum Thema Rapid-Prototyping gibt es hier:<br /> <a href="http://t3n.de/magazin/schnell-kommt-ohne-neue-buchstaben-erfinden-222480/">t3n-rapidprototyping</a></p> <p>Weiterhin, für alle Rails-Jünger natürlich unverzichtbar sind die Screencasts von Ryan Bates, die einfach nur Spaß machen, und meiner Meinung nach zu den besten (frei erhältlichen) Screencasts im Netz gehören!<br /> <a href="http://railscasts.com/episodes/">railscasts</a></p> <p>Als Umsteiger von <span class="caps">PHP</span> fand ich diese Seite auch noch recht interessant, in der äquivalente Rails/Ruby-Konstrukte gängige <span class="caps">PHP</span>-Funktionen abgebildet werden :)<br /> <a href="http://railsforphp.com/reference/strings/htmlentities">rails4php</a></p> tag:www.stefanwienert.net,2008:Post/6 2009-08-14T08:49:00Z 2009-10-18T10:49:01Z Notenübersichts-Bot in PHP <p>Vor zwei Wochen kam mir beim Überprüfen der aktuellen Notenergebnisse die Idee in den Sinn, das Ganze zu automatisieren und als Feed zur Verfügung zu stellen, um es in meinen Feedreader mit einzubinden und so immer auf dem aktuellen Stand sein zu können ;).</p> <p>Der Einfachheit halber hab ich <span class="caps">PHP</span>/Curl genommen, da ich kurz mal in Ruby reingeschaut hatte, mir die <span class="caps">HTTP</span>-Bibliothek aber nicht zweckdienlich erschien.</p> <h3>Teil 1: Den Quelltext der Webseite holen</h3> <p>Dazu mal mit Firefox und LiveHTTP-Headers-Addon schauen, was man beim Login zu alles schickt. In unserem Fall muss man danach noch einen Klick auf “Notenübersicht” machen.</p> <p>Das ganze dann in cURL gießen und eine cookies.txt schreibbar bereitstellen:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt>15<tt> </tt>16<tt> </tt>17<tt> </tt>18<tt> </tt>19<tt> </tt><strong>20</strong><tt> </tt>21<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"> <span class="lv">$username</span>=<span class="s"><span class="dl">&quot;</span><span class="k">Meine Matrikelnummer</span><span class="dl">&quot;</span></span><tt> </tt> <span class="lv">$passwort</span>=<span class="s"><span class="dl">&quot;</span><span class="k">Mein Passwort ;)</span><span class="dl">&quot;</span></span><tt> </tt> <span class="lv">$ch</span> = curl_init();<tt> </tt> <span class="c">//Variablen setzen</span><tt> </tt> <span class="lv">$url</span>=<span class="s"><span class="dl">&quot;</span><span class="k">https://wwwqis.htw-dresden.de/qisserver/rds?state=user&amp;type=1&amp;category=auth.login&amp;startpage=portal.vm</span><span class="dl">&quot;</span></span>;<tt> </tt> <span class="c">//$url=&quot;https://wwwqis.htw-dresden.de/qisserver/rds?state=user&amp;amp;type=1&amp;amp;category=auth.login&amp;amp;startpage=portal.vm&quot;;</span><tt> </tt> <span class="lv">$arrSubmit</span>=<span class="s"><span class="dl">&quot;</span><span class="k">username=</span><span class="lv">$username</span><span class="k">&amp;submit=%C2%A0Ok%C2%A0&amp;password=</span><span class="lv">$password</span><span class="dl">&quot;</span></span>;<tt> </tt> <span class="lv">$cookies</span>=<span class="s"><span class="dl">&quot;</span><span class="k">cookie.txt</span><span class="dl">&quot;</span></span>;<tt> </tt><span class="c">//Session Optionen setzen</span><tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_URL</span>,<span class="lv">$url</span>);<tt> </tt> curl_setopt (<span class="lv">$ch</span>, <span class="co">CURLOPT_POST</span>, <span class="i">1</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_POSTFIELDS</span>, <span class="lv">$arrSubmit</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_HEADER</span>, <span class="i">0</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_COOKIEJAR</span>, <span class="lv">$cookies</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_COOKIEFILE</span>, <span class="lv">$cookies</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_FOLLOWLOCATION</span>, <span class="pc">true</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_RETURNTRANSFER</span>, <span class="pc">true</span>);<tt> </tt><span class="c">//Ausf?hren der Aktionen</span><tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_SSL_VERIFYPEER</span>, <span class="pc">FALSE</span>);<tt> </tt> <span class="lv">$result</span>=curl_exec(<span class="lv">$ch</span>);<tt> </tt> curl_close(<span class="lv">$ch</span>);<tt> </tt></pre></td> </tr></table> <p>In unserem Beispiel des <span class="caps">HIS</span>-<span class="caps">QIS</span> gibt es noch eine Art zweiter Session-ID, die ausgelesen werden, und mit übergeben werden muss:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"> <span class="pd">preg_match</span>(<span class="s"><span class="dl">&quot;</span><span class="k">/asi=([^</span><span class="ch">\&quot;</span><span class="k">]*)</span><span class="ch">\&quot;</span><span class="k">/</span><span class="dl">&quot;</span></span>,<span class="lv">$result</span>,<span class="lv">$treffer</span>);<tt> </tt> <span class="lv">$asi</span>=<span class="lv">$treffer</span>[<span class="i">1</span>];<tt> </tt></pre></td> </tr></table> <p>Dann der Zweite durchlauf mit der asi:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt>12<tt> </tt>13<tt> </tt>14<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"> <span class="lv">$url</span>=<span class="s"><span class="dl">&quot;</span><span class="k">https://wwwqis.htw-dresden.de/qisserver/rds?state=htmlbesch&amp;moduleParameter=Student&amp;menuid=notenspiegel&amp;asi=</span><span class="lv">$asi</span><span class="dl">&quot;</span></span>;<tt> </tt> <span class="lv">$ch</span> = curl_init();<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_URL</span>,<span class="lv">$url</span>);<tt> </tt> curl_setopt (<span class="lv">$ch</span>, <span class="co">CURLOPT_POST</span>, <span class="i">0</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_HEADER</span>, <span class="i">0</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_COOKIEJAR</span>, <span class="lv">$cookies</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_COOKIEFILE</span>, <span class="lv">$cookies</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_FOLLOWLOCATION</span>, <span class="pc">true</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_RETURNTRANSFER</span>, <span class="pc">true</span>);<tt> </tt> curl_setopt(<span class="lv">$ch</span>, <span class="co">CURLOPT_SSL_VERIFYPEER</span>, <span class="pc">FALSE</span>);<tt> </tt> <span class="lv">$result</span>=curl_exec(<span class="lv">$ch</span>);<tt> </tt> <span class="c">//echo curl_error($ch);</span><tt> </tt> <span class="c">//Session beenden</span><tt> </tt> curl_close(<span class="lv">$ch</span>);<tt> </tt></pre></td> </tr></table> <p>Damit ist der Text in der Variablen $result<br /> Extraktion der wichtigen Zeilen mit XPath</p> <p>Mittels Firebug schauen, wo die Prüfungsergebnisse drinstehen und die XPaths kopieren bzw. analysieren. Damit erhalten wir eine NodeList die wir ausgeben/speichern können:</p><table class="CodeRay"><tr> <td class="line_numbers" title="click to toggle" onclick="with (this.firstChild.style) { display = (display == '') ? 'none' : '' }"><pre>1<tt> </tt>2<tt> </tt>3<tt> </tt>4<tt> </tt>5<tt> </tt>6<tt> </tt>7<tt> </tt>8<tt> </tt>9<tt> </tt><strong>10</strong><tt> </tt>11<tt> </tt></pre></td> <td class="code"><pre ondblclick="with (this.style) { overflow = (overflow == 'auto' || overflow == '') ? 'visible' : 'auto' }"><span class="lv">$Doc</span> = <span class="r">new</span> <span class="co">DOMDocument</span>();<tt> </tt><span class="lv">$Doc</span>-&gt;loadHTML(<span class="lv">$result</span>);<tt> </tt><span class="lv">$Doc</span>-&gt;preserveWhiteSpace = <span class="pc">false</span>;<tt> </tt><span class="lv">$Doc</span>-&gt;normalizeDocument();<tt> </tt><span class="lv">$XPath</span> = <span class="r">new</span> <span class="co">DOMXPath</span>(<span class="lv">$Doc</span>);<tt> </tt><span class="lv">$NodeList</span> = <span class="lv">$XPath</span>-&gt;query(<span class="s"><span class="dl">&quot;</span><span class="k">//tr[@bgcolor='#EFEFEF']</span><span class="dl">&quot;</span></span>);<tt> </tt><span class="r">foreach</span> (<span class="lv">$NodeList</span> <span class="r">as</span> <span class="lv">$node</span>)<tt> </tt>{ <tt> </tt> <span class="pd">echo</span> <span class="lv">$node</span>-&gt;nodeValue;<tt> </tt> ...<tt> </tt>}<tt> </tt></pre></td> </tr></table> <p>Schon fast fertig, was fehlt noch?</p> <ul> <li>Die Datumswerte auslesen, nach Sekunden umwandeln um danach die Noten danach zu sortieren.</li> </ul> <ul> <li>Einfacher Caching Algorithmus à la “Wenn unsere cache-datei älter als 30 minuten ist, dann erstelle sie neu [durchlaufe den Algorithmus] und schreibe das Ergebnis in die Cache-Datei; andernfalls gib nur die cache-Datei aus”</li> </ul> <ul> <li>Ausgabe als <span class="caps">RSS</span>-Feed, einfach mal die Spezifikation googlen ;)</li> </ul>