Jan
31

Eigene Vorlagen für Scaffolding und von Bake erzeugte Views

Scaffolding ist ja CakePHP Programmierung für ganz Faule. Naja, nicht nur, weil man damit ja schon recht einfach und vorallem schnell die notwendigen Dateien für Model, Controller und auch die Views erzeugen kann. Aber es geht natürlich auch mit

1
    var $scaffold;

das Scaffolding zu nutzen. CakePHP erzeugt dann keine Views oder sondern nutzt die zur Verfügung gestellten Vorlagen.

Genau um die Vorlagen gehts in dem Posting von teknoid, der das wohl von Mark Story im IRC-Chat aufgeschnappt hat.

Hier die Tipps zum Nachvollziehen:

Kopiere auf dem Verzeichnis cake/libs/view/scaffolds die Dateien für die entsprechenden Views - also

edit.ctp
index.ctp
view.ctp

ins eigene app/views/scaffolds Verzeichnis. Dabei wird übrigens für add und edit beidesmal edit.ctp eingesetzt. In app/views/scaffolds können die Views jetzt beliebig angepasst werden, bis sie euch gefallen.

Wenn man sich allerdings von der cake console mit Hilfe von bake die Views erzeugen lässt, dann macht man sich auch Kopien und zwar aus cake/console/libs/templates/views werden die Dateien

form.ctp
home.ctp
index.ctp
view.ctp

nach app/vendors/shells/templates/views kopiert und können dort einfach wieder verändert werden. Achtung das Verzeichnis views muss wohl erst noch erstellt werden. Also nicht wundern.

So und nun happy baking.

Jan
30

Behaviors - meine Best-of

Als Behaviors in CakePHP vorgestellt wurden - ja ist schon eine zeitlang her - konnte ich nicht wirklich viel damit anfangen, da ich die Vorteile nicht wirklich gesehen habe. Vielleicht lag es aber auch daran, dass die Dokumentation gefehlt hat, was sich inzwischen geändert hat oder weil die Beispiele mir unverständlich erschienen.

Inzwischen ist die Liste der veröffentlichten Behaviors in der Bakery ziemlich lang geworden. Ich habe über 40 - in Worten vierzig - Behaviors gezählt und das ist doch schon eine ziemlich lange Liste. Einige der Behaviors sind mittlerweile in den Core eingebaut worden, wie beispielsweise das CounterCache-Behavior (direkt in das Model) und das Containable Behavior. Ein Einsatz von Behaviors ist ja denkbar einfach und auch im Book recht gut beschrieben. Behaviors sind ja sowas wie die Komponenten der Controller und erweitern die Funktionalität des Models. Also werden sie auch im Model angewendet.

1
2
3
class Category extends AppModel {    
    var $name   = 'Category';    
    var $actsAs = array('Tree');}

So hier meine kleine Best-of-Liste:

  • Sluggable Behavior, das ja Sebastian schon vorgestellt, bzw. erweitert hat. Mittlerweile kann man hier auch Inflector::slug() nutzen.
  • Polymorphic Behavior von Andy Dawson (AD7six), das sehr nützlich ist, wenn man ein Modell mit mehreren anderen Modellen verknüpfen will. Beispielsweise lassen sich damit Notizen an andere Modell anhängen ohne, dass man mehrere Notizen bräuchte. Sehr kuhl.
  • Cipher-Behavior von Xemle, das einzelne Teile einer Tabelle, bzw. eines Models verschlüsselt und damit vor fremden Augen schützt.
  • Containable Behavior, wie ja bereits schon gesagt, steht es mittlerweile im Core zur Verfügung und gehört zu meinen absoluten Rennern, die bei der Optimierung von Model::find() Zugriffen helfen
  • Image Behavior, das ist ja auch schon kurz erwähnt hatte
  • NamedScope Behavior, das ich nicht in der Bakery gefunden habe, sondern via m3nt0r bei Joel Moss gefunden habe.

So das wäre erstmal meine kleine Liste. Ich hab noch ein paar auf der Pfanne, die ich noch verstellen werde, wenn ich ein wenig mehr damit gemacht habe. Hab ich ein wirklich gutes Behavior vergessen?

Jan
29

HtmlHelper und die Ausgabeformatierung

Der HtmlHelper gehört für die Ausgabeformatierung zu den Standardwerkzeugen in CakePHP, da der Links erzeugen, Bilder ausgeben und noch viel mehr kann.

Meist schreib ich inzwischen auch normalen HTML-Code mit Hilfe des Helpers, da ich mir dann keine Gedanken mehr über die Formatierung machen muss.

1
2
     $html->tag('h2', __('Meine Überschrift', true));
     $html->para('grosseSchrift', __('die Erklärung zu dem Kapitel', true));

liefert dann schon

   <h2>Meine Überschrift</h2>
   <p class="grosseSchrift">die Erklärung zu dem Kapitel</p>

Dabei nutzt der HtmlHelper intern eine Feld mit Tags, die im Quelltext vom HtmlHelper ganz am Anfang definiert werden. Die Tags kann man für eigene Zwecke auch ganz einfach anpassen.

1
     $html->tags['link'] = "<a rel="nofollow" href="%s"%s>%s</a>";

Damit werden dann Links erzeugt, die immer rel=”nofollow” enthalten, wenn man das für seine Zwecke braucht. Oder man man auch ein Neue Zeile “\n” anhängt, damit man lesbarere Quelltexte erhält ;)

Um einen Bild und eine Url getrennt mit Links zu versehen kann das dann beispielsweise so aussehen:

1
2
3
       $txt = $this->Html->link(
                 $this->Html->image($image), $link, array('class'=>'img'), false, false) . " " .
                 $this->Html->link($title, $link);

Übrigens sind jetzt in der neuen API-Dokumentation auch die Parameter und Methoden alphabetisch aufgelistet, was das Ganze noch viel benutzbarer macht. Jetzt müssen eigentlich nur noch die Funktionen direkt über die Suche zu finden sein.

Jan
28

CakePHP mit neuer API-Dokumentation

Gerade lese ich im Google Reader bei Mark Story, daß die API-Dokumentation mit einem neuen Generator erstellt wird. Sieht deutlich übersichtlicher aus, als die alte Version der API. Allerdiings fehlt mir die graphische Übersicht über die Klassenabhängigkeiten.

Der neue API-Generator ist jetzt in CakePHP geschrieben und der Quelltext steht auf theChaw zur Verfügung. Voraussetzung ist equires PHP5.2, wenn man es selber einsetzen möchte. Sehr schön und vielen Dank an die Entwickler.

Update: im Augenblick sieht es leider so aus, als ob im API-Generator noch einige Bugs sind. Die Suche wurde ja schon bemängelt und da wird auch dran gearbeitet. Allerdings funktionieren im Augenblick die Links auf die Quelltext noch nicht und einige Klassen wie FormHelper, RssHelper und wohl noch weitere sind leider ein wenig zerschossen. Aber ich denk mal, dass das in Kürze behoben sein wird. Einige Tickets sind dazu bereits in TheChaw eingetragen.

Update2: Mittlerweile sind die ersten Bugs wohl schon draußen und die Verlinkung mit den Quelltexten funktioniert.

Jan
27

Wer hat denn den Datensatz erzeugt oder geändert

CakePHP hat ja eine sehr praktischen Automatismus und speichert in die Datenbankfelder created und modified entsprechend das aktuelle Datum ab, wenn die Felder vorhanden und NULL sind. Modified wird bei jedem Update automatisch neu gesetzt. Das klappt bei jedem Model::save(), aber nicht bei Model::updateAll() hab ich übrigens festgestellt, da bei updateAll() intern SQL-Queries erzeugt werden in der Form:

1
  UPDATE modelname SET feld1 = 12 WHERE feld2 = Wert

Wäre es nicht praktisch, wenn man unterschiedliche Benutzer hat, dass auch die Information, welcher Benutzer den Datensatz erzeugt bzw. geändert hat, gespeichert wäre.
Die AuthComponent stellt ja in CakePHP eine recht handliche Möglichkeit bereit den Zugang zu einer Anwendung zu regeln. Dabei hat man dann eine Tabelle mit den Usern die unter anderem eben auch die UserId enthält.
Auth ist auch so nett und speichert in der aktuellen Session die Informationen über den eingeloggten Benutzer. Allerdings kommt man nicht so leicht an diese Informationen, wenn man versucht im Model::beforeSave() den Benutzer in den Datensatz einzutragen.

1
2
3
4
5
6
7
8
9
10
CREATE TABLE articles (
  id int UNSIGNED NOT NULL AUTO_INCREMENT,
  title varchar(255) NOT NULL,
  article varchar(255) NOT NULL,
  created datetime,
  creator_id int UNSIGNED,   # soll über LoadsysAuth gesetzt werden
  modified datetime,
  modifier_id int UNSIGNED,   # soll über LoadsysAuth gesetzt werden
  PRIMARY KEY(id)
);

Vor einiger Zeit habe ich die LoadsysAuth Komponente gefunden, die genau an dieser Stelle hilft. Denn sie kann im Model auf die Session-Informationen zugreifen und den Benutzer im Datensatz eintragen.
Das sieht dann in etwa so aus wenn man es gleich in das beforeSave() im app_model.php einträgt, kann man seine Tabelle um die Felder creator_id und modifier_id erweitern und im beforeSave() werden die Werte dann von der LoadsysAuth gesetzt.

1
2
3
4
5
6
7
8
9
10
11
12
class AppModel extends Model {
        function beforeSave() {
                $exists = $this->exists();
                if ( !$exists && $this->hasField('creator_id') && empty($this->data[$this->alias]['creator_id']) ) {
                        $this->data[$this->alias]['creator_id'] = LoadsysAuth::getUserId();
                }
                if ( $this->hasField('modifier_id') && empty($this->data[$this->alias]['modifier_id']) ) {
                        $this->data[$this->alias]['modifier_id'] = LoadsysAuth::getUserId();
                }
                return true;
        }
}

Wenn man die LoadsysAuth Komponente einsetzt, dann sollte man allerdings darauf achten, dass sämtlich Auth Aufrufe durch LoadsysAuth ersetzt werden. Die Komponente ist nämlich direkt von der Auth Komponente abgeleitet und stellt alle Funktionen von Auth zur Verfügung. Zusätzlich kann man allerdings mit Funktionen wie

1
2
LoadsysAuth::getUser(); // liefert den kompletten User Datensatz
LoadsysAuth::getUserId(); // liefert nur die Id des Users

auch im Model auf die entsprechenden Benutzerdaten zugreifen. Bei meinem letzten Projekt hat mir das enorm geholfen und einiges an extra Arbeit erspart. Dabei sind die Funktionen in LoadsysAuth als static-Funktionen implementiert und können recht einfach erweitert bzw. angepasst werden.

Hier gehts zur LoadsysAuth Komponente, wo sie auch heruntergeladen werden kann.

Jan
26

FileUploader in CakePHP für Bilder

FileUploader sind ja die Teile mit denen man in Web2.0 und auch sonstigen Anwendungen Bilder und ähnliches hochlädt. Vorgestern hab ich mich noch mit Sebastian drüber unterhalten und er meinte, er nutzt den phpThumb Helper aus der Bakery, weil der die Bilder einfach cached. Sprich, wenn ein neues Format gebraucht wird, dann wird über die phpThumb Bibliothek das neue Bild erzeugt und im Cache zwischengespeichert.

Der Aufruf sieht damit im View eigentlich ziemlich lässig aus:

1
     $thumbnail->show($options, $tag_options);

Allerdings muss man sich dazu noch selber merken, welche Bilder vorhanden sind und wie die heißen, sprich doch ein Model bemühen oder entsprechendes.

Selber benutze ich in meiner aktuellen Anwendung das image-Behavior, das einen komplett anderen Ansatz geht.
Als Behavior wird es im Model mit $actsAs aufgesetzt und erhält die notwendigen Parameter. D.h. im Falle des Image-Behaviors wird über den Parameter fields das Attribut in der Datenbanktabelle festgestellt - hier avatar - und dann werden die Parameter eingestellt für die unterschiedlichen Thumbnails, die das Behavior beim Speichern des Datensatzes generieren soll.

1
2
3
4
5
6
7
8
9
10
    var $actsAs = array(
        'Image'=>array(
		'fields'=>array(
			'avatar'=>array(
				'thumbnail'=>array('create'=>true),
				'resize'=>array('width'=>'55', 'height'=>'55','aspect'=>true),
				'versions'=>array(
					array('prefix'=>'s','width'=>'32','height'=>'32','aspect'=>true),
					array('prefix'=>'16','width'=>'16','height'=>'16','aspect'=>true)
		)))));

In meinem Fall wird also ein thumbmail erzeugt, eine resize auf 55px Kantenlänge vorgenommen und zusätzlich noch zwei Versionen erzeugt, einmal mit dem Prefix ’s’ und einer Kantenlänge von 32, sowie einem Prefix ‘16′ mit einer entsprechenden Kantenlänge.
In der Datenbank selbst wird nur das Feld avatar gespeichert, das den Dateinamen enthält, den man angibt. Alle anderen Felder werden virtuell erzeugt. Hier ein Beispiel wie der Datensatz nach einem $this->find zurückkommt:

1
2
3
4
5
6
7
            [avatar] => Array
                (
                    [path] => thumbs/Person/812/avatar.skypeavatar.jpeg
                    [thumb] => thumbs/Person/812/thumb_avatar.skypeavatar.jpeg
                    [s] => thumbs/Person/812/s_avatar.skypeavatar.jpeg
                    [16] => thumbs/Person/812/16_avatar.skypeavatar.jpeg
                )

Wie man schön sieht bekommt man also deutlich mehr Informationen über das vom Image-Behavior verarbeitete Bild zurück und kann damit entsprechend weiterarbeiten.

In der Bakery ist gerade ein neuer Beitrag über einen FileUploader schienen. Ist aber leider gerade nicht mehr online, weil er wahrscheinlich zum Editieren/Erweitern vom Autor verändert worden ist und erst neu freigeschaltet werden muss. Sah aber auch ganz interessant aus, weil er insbesondere die Möglichkeit vorgesehen hat, dass man ins Model speichern kann, aber nicht unbedingt muss. Ich werd den Link hier noch setzen, sobald der Artikel in der Bakery wieder zur Verfügung steht.

Welche Helper, Behaviors oder anderen Methoden verwendet ihr denn, um mit hochgeladenen Bildern umzugehen?

Jan
25

Kleine Tricks

Vieles in CakePHP lässt sich leichter programmieren, wenn man den einen oder anderen “Trick” kennt, der einem hilft schneller ans Ziel zu kommen.

$this->data im View nutzen

Wenn man im Controller Daten abholt und dann gleich an this->data zuweist, dass man die Daten nicht extra an den View weitergeben muss, sondern steht als this->data gleich zur Verfügung.

1
     $this->data = $this->MyModel->find('all');

Das allseits bekannte set muss dann nicht ausgeführt werden. Also kann man sich das

1
     $this->set('data', $this->data);

sparen, sondern kann im View einfach direkt weiterarbeiten:

1
2
3
4
     // ausgabe.ctp
     foreach ($this->data as $data) {
         // ausgabe von $data
     }

Wenn man das konsequent anwendet lassen sich viele Views recht schnell umsetzen, da man nicht ständig das $thi->set nutzen muss. Falls man übrigens mehrere Datenbereiche abholen und an den View übergeben will, dann nutzt man am Besten compact() also wie hier beispielsweise:

1
2
3
4
5
     $products = $this->Product->find('all');
     $categories = $this->Category->find('all');
     $customers = $this->Customer->find('all');
     // und jetzt mit einem set()
     $this->set(compact('products', 'categories', 'customers'));

Damit stehen dann im View alle drei Variablen $products, $categories, $customers bereit.

$id nutzen für das Update

Der zweite kleine Trick den ich hier am Sonntag noch schnell zur verfügung stellen will hat mit dem Update von einzelnen Feldern zu tun. Der umständliche Weg sieht in etwa so aus:

1
2
3
     $this->data = $this->MyModel->find('first', array('conditions'=>array('id' => $id)));
     $this->data['MyModel']['meinfeld'] = 'neuer Feldinhalt';
     $this->save($this->data);

Aber es geht in dem Fall ein wenig einfacher und zwar so:

1
2
     $this->MyModel->id = $id;
     $this->MyModel->saveField('meinfeld', 'neuer Feldinhalt');

So das war heute der Sonntagsbeitrag. Morgen gibts wieder etwas Ausführlicheres.

Jan
24

Sexy URLs mit CakePHP oder: das Sluggable-Behavior

Worum geht’s?

Hier möchte ich das Sluggable-Behavior vorstellen und zeigen, wie man es einbindet und auf den deutschen Sprachraum anpasst.

Warum das Ganze
Richtig hübsche und zudem suchmaschinenoptimierte URLs sehen meiner Meinung nach nicht aus wie “doma.in/artikel/view/32″ sondern eher wie “doma.in/artikel/ich-bin-ein-toller-artikel”. Auch wenn es in der Suchmaschinenoptimierung nur ein kleiner Faktor ist, so kann es der entscheidende Faktor sein. Der weit größere Vorteil einer solchen “spechenden URL” ist aber, dass sie in den Suchergebnissen auch häufiger angeklickt werden wird, da der Nutzer eine konkretere Vorstellung davon hat, was ihn erwartet.
more…

Jan
24

SMS-Gateway als CakePHP Componente

Für eine Anwendung, die ich gerade umsetze, brauche ich unter anderem auch die Möglichkeit SMS Meldungen zu versenden. Dazu geht man am Einfachsten her und sucht sich von den zahlreichen SMS-Providern einen aus, der einem zusagt. Die Provider bieten in der Regel unterschiedliche Schnittstellen an. E-Mail und HTTP sind so die üblichen Schnittstellen. Ich habe mich für Mobilant entschieden und dort für die HTTP-Schnittstelle. Im großen und Ganzen hat man ein einfaches REST-Interface, sprich alle Daten werden über einen HTTP-Aufruf an den Provider übermittelt.

Um nicht mit urlencoding und dem Zusammenbau der URL zu stolpern während der Programmierung des SMS-Versands, habe ich mir eine übersichtliche Komponente erstellt. Für die Kommunikation mit HTTP-Servern bietet CakePHP 1.2 von Haus aus eine HttpSocket Bibliothek an, die recht einfach zu nutzen ist. Für die Dokumenation zu HttpSocket steht im Augenblick nur die API zur Verfügung. Im Book findet sich dazu leider noch nichts. Wird aber sicherlich noch kommen.

HttpSocket wird zunächst mit App::import geladen und im Konstruktor initalisiert. Danach ist es eigentlich ein leichtes die send() zu Schreiben. Für einen GET Aufruf übergibt man an HttpSocket::send() die URL des Anbieters und übergibt im zweiten Parameter als Array die Parameter für den HTTP-Aufruf.

Bei Mobilant liegt die Schnittstelle auf:

Damit die Komponenten später auch einfach auf anderen SMS-Gateways angepasst werden kann, hab ich mir ein kleines Array $service_apis aufgebaut, das die URL enthält und die Beschreibung für die Parameter die erwartet werden. Bei Mobilant und auch bei den anderen Anbietern benötigt man in der Regel Zugangsinformationen oder eben einen Schlüssel (key) der dafür sorgt, dass das Absenden möglich wird und vorallem auch, damit der SMS-Provider mir eine Rechnung über die versandten SMS-Nachrichten stellen kann.

Die Anwendung der Komponente ist recht trivial. Im Controller wird die Komponente über

1
      $var components = array('SmsGateway');

eingebunden und dann kann durch einen Aufruf in der Art:

1
2
     $this->SmsGateway->send('01701234567', 
                 'Das ist eine SMS Nachricht mit CakePHP versandt');

genutzt werden.

Als dritten Parameter von send() könnte man noch einen anderen SMS-Provider auswählen, wenn die Daten entsprechend in dem Array $service_api hinterlegt wären.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
    App::import('Core', array('HttpSocket'));
    /**
     * SMS Gateway
     * Documentation can be found on:
     * http://www.mobilant.de/system/m_navInf.php?infName=documents
     * PDF-Document
     */
    class SmsGatewayComponent extends Object {
        var $Http = null;
        var $service_apis = array(
            'mobilant'=>array('url'=>'https://gateway2.mobilant.net/index.php',
                              'params'=>array('key'=>'xxxxxxxxxxxxxxxxxxx',
                                              'message'=>'%message%',  // external parameter
                                              'receiver'=>'%receiver%', // external parameter
                                              'service'=>'sms'
                                              ),
                              ),
            );
 
        /**
         * __construc -- constructor to initialize the HttpSocket Object to access the services
         *
         * @author: SHirsch
         * @created: 09.01.2009
         */
        function __construct() {
            $this->Http =& new HttpSocket();
        }
 
        /* function
        **
        ** @created: 05.01.2009 12:54:43
        ** @param string $message that should be send via Provider
        ** @param strng $receiver the phone number of the receiving cell phone
        ** @param string $service_api name of the url shortener to be used by create as in $this->service_apis
        ** @return the error from the HTTP request if an error was encountered
        */
        function send($message = null, $receiver = null, $service_api = 'mobilant')
        {
            if (!empty($message) && !empty($receiver)) {
                if (array_key_exists($service_api,  $this->service_apis)) {
                    $params = $this->service_apis[$service_api]['params'];
                    $params['message'] = utf8_encode($message);
                    $params['receiver'] = $receiver;
                    return $this->Http->get($this->service_apis[$service_api]['url'], $params);
                }
            }
            return false;
        }
 
        /* check_phone_number
        **
        ** @created: 15.01.2009 13:53:59
        ** @param type $var
        **
        */
        function check_phone_number($phone)
        {
            return $phone;
        }
    }

Ein paar Erweiterungen sind noch notwendig, wie beispielsweise die Prüfung der Telefonnummer mit Hilfe der Funktion check_phone_number() auf gültige Nummern im Inland oder ähnliches.

Jan
23

CakePHP, die CakeBar und warum ich hier blogge

Noch ein Blog über CakePHP?

Ja! und zwar auf deutsch, denn davon gibts hier noch nicht allzu viele und CakePHP wird seit der Version 1.2, die ja seit diesem Jahr in einem Final Release freigegeben worden ist, wird immer interessanter. In Englisch und teilweise auch in anderen Sprachen wie Spanisch und Japanisch gibt es bereits mehrere Blogs und die Core Developer von CakePHP zu denen auch ein paar Deutsche gehören, bloggen ausnahmslos in Englisch. Sicherlich macht das Sinn, aber da ich denke, dass sich CakePHP in nächster weiter verbreiten wird, bietet ein deutsches Blog eine gute erste und vielleicht auch zweite Anlaufstelle für Einsteiger und Fortgeschritte.

Bei der letzten CakeBar, die ich mit Sebastian zusammen in München aus der Taufe gehoben habe, haben wir kurz drüber gesprochen, daß wir das Projekt CakePHP Blog vorantreiben wollen und Sebastian meinte, er hat auch schon einen Beitrag in der Schublade liegen. Also Sebastian, das Blog ist aufgesetzt und bereit für die Aufnahme von Postings rund um das Thema Webentwicklung mit CakePHP.

Weitere Autoren erwünscht, die auf Deutsch über CakePHP berichten wollen.

Damit das Blog mit interessanten Beiträgen gefüllt wird, suchen wir noch weitere Kollegen und Entwickler, die mit CakePHP unterwegs sind und hier Fulltime oder auch mit Gastbeiträgen mitschreiben wollen.

Ein weiterer Grund, warum ich hier über CakePHP bloggen will, begründet sich in der Tatsache, dass man neue Ideen vorstellen und diskutieren kann, dass sich mit der Zeit ein Verzeichnis von nützlichen Beiträgen und Hinweisen ansammelt und dass ich ein Überblick über die Entwicklung und über vorhandenen Componenten, Helper, Datasources und Behaviors oder Plugins entstehen soll. Einer der Vorteile von CakePHP und generell der Entwicklung mit einem Framework ist ja der, daß das Rand nicht jedesmal neu erfunden werden muss. Erfahrungsberichte über eingesetzten Tools und CakePHP Erweiterungen sollen das Ganze abrunden und ergänzen.

Also meldet euch einfach hier in den Kommentaren oder schreibt mich per Mail an, wenn ihr Spass dran habt, aktiv an dem Blog mitzuwirken.

Oben