Web-Applikationen mit dem Yii-Framework

Web-Applikationen entwickeln ist meist mit unzähligen Standard-Aufgaben wie Formular-Validierung, Authentifizierung und Unmengen an HTML-Code verbunden. Programmierer scheuen sich deshalb meist davor, eine Applikation mit Web-Technologien umzusetzen. Vergessen Sie diese Vorurteile und lernen Sie das Yii-Framework kennen.

In diesem Artikel möchte ich an einem praktischen Beispiel aufzeigen, wie einfach es ist, mit dem Yii-Framework eine Web-Applikation zu erstellen. Als Beispiel wird ein simples Verwaltungsprogramm für eine Bibliothek erstellt. Vor dem Eintauchen in die eigentliche Materie möchte ich den Unterschied zwischen einem CMS (z.B. Joomla, WordPress, TYPO3, etc.) und einem Framework erläutern.

Ein CMS bietet die Möglichkeit den Inhalt einer Website auf einfachste Art und Weise zu verwalten. Grundfunktionalitäten sind bereits ausprogrammiert und Sie brauchen sich nur noch um die Menustruktur und den Inhalt zu kümmern.
Im Gegensatz dazu handelt es sich bei einem Framework um einen "Werkzeugkasten" für Programmierer. Die Logik müssen Sie programmieren, jedoch nimmt Ihnen das Framework triviale Arbeiten wie Datumsumrechnungen oder das Speichern von Datensätzen ab. Eine solche Werkzeugsammlung ist das Yii-Framework. Yii ist gratis und unter Einhaltung der BSD-Lizenz zur Verwendung freigegeben.

Wie Sie das Yii-Framework installieren, wird auf der Tutorialseite des Frameworks erläutert. Damit Sie loslegen können, brauchen Sie folgende Software auf Ihrer Maschine:



1.Wieso das Yii-Framework

Es gibt unzählige PHP-Frameworks. Was also unterscheidet das Yii-Framework von ähnlichen Produkten wie dem Zend-Framework, CakePHP und CodeIgniter? Auch bei Yii handelt es sich um ein MVC-Framework mit ähnlichem Funktionsumfang wie die Produkte der Konkurrenz. Nach einigen Jahren Erfahrung mit dem Produkt würde ich die Frage mit den folgenden Wörtern beantworten: Geschwindigkeit und Einfachheit. Ausserdem ist Yii keine Insellösung, sondern lässt sich problemlos mit Teilen von Drittframeworks erweitern.

Yii bedeutet "yes it is" und liefert bereits im Namen die Antwort auf Fragen wie "ist es schnell?" oder "ist es sicher?". Detaillierte Speed-Messungen und Erläuterungen dazu finden Sie auf der offiziellen Website. Was es mit der Einfachheit auf sich hat, wird in den nächsten Abschnitten deutlich.

2.Database First

Bei der Entwicklung einer Web-Applikation mit Yii beginnen Sie in der Regel mit der Datenstruktur. Das Yii-Framework kann mit unzähligen Datenbanksystemen umgehen. In diesem Beispiel arbeiten wir mit einem der populärsten: MySQL.

Abstrahiert man die realen Objekte einer Bibliothek, erhält man die folgenden Tabellen:

Folgende Beziehungen zwischen den Objekten werden angenommen:

Das resultierende Datenbankschema ist auf nebenstehendem Bild ersichtlich.

DB-Schema

3.Code-Generierung mit Gii

Ja, Sie haben richtig gelesen. In diesem Schritt wird Quellcode generiert. Nach anfänglicher Skepsis war ich schnell von diesem Feature überzeugt. Die Logik hinter "Gii" - so der Name der Generierungs-Tools - ist ausgefeilt und der generierte Code sauber.

Die Generierung erfolgt in zwei Schritten:

  1. Generieren der Models anhand der bestehenden Datenbank-Tabellen und deren Relationen
  2. Generieren der Views & Controllern

Model

So sieht die Model-Datei der Tabelle book aus, welche Yii generiert hat:

<?php

/**
 * This is the model class for table "book".
 *
 * The followings are the available columns in table 'blog':
 * @property integer $id
 * @property integer $client_id
 * @property string $title
 *
 * The followings are the available model relations:
 * @property Client $borrowedBy
 * @property Author[] $authors
 */
class Book extends CActiveRecord
{
	/**
	 * Returns the static model of the specified AR class.
	 * @param string $className active record class name.
	 * @return Blog the static model class
	 */
	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}

	/**
	 * @return string the associated database table name
	 */
	public function tableName()
	{
		return 'book';
	}

	/**
	 * Defines validation rules for the db-fields.
	 * @return array validation rules for model attributes.
	 */
	public function rules()
	{
		return array(
			array('title', 'required'),
			array('title', 'length', 'max'=>128),
			array('client_id', 'exists', 'className'=>'Client', 'attributeName'=>'id'),
			array('id, client_id, title', 'safe', 'on'=>'search'),
		);
	}

	/**
	 * Defines the relations between the models for automatic relation
	 * lookup
	 * @return array relational rules.
	 */
	public function relations()
	{
		return array(
			'borrowedBy' => array(self::BELONGS_TO, 'client', 'client_id'),
			'authors' => array(self::MANY_MANY, 'Author', 'author_book(book_id, author_id)'),
		);
	}

	/**
	 * Returns the labels of the db-columns
	 * @return array customized attribute labels (name=>label)
	 */
	public function attributeLabels()
	{
		return array(
			'id'=>'ID',
			'client_id'=>'Ausgeliehen an',
			'title'=>'Buchtitel',
		);
	}

	/**
	 * Retrieves a list of models based on the current search/filter conditions.
	 * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
	 */
	public function search()
	{
		$criteria = new CDbCriteria();
		$criteria->compare('id', $this->id, false);
		$criteria->compare('client_id', $this->client_id, false);
		$criteria->compare('title', $this->title, true);

		return new CActiveDataProvider($this, array('criteria'=>$criteria));
	}

	/**
	 * Scope to exclusively look for borrowed or unborrowed books
	 * @param boolean $borrowed whether or not to look for borrowed books
	 * @return Book this
	 */
	public function scopeIsBorrowed()
	{
		$crit = new CDbCriteria();
		$crit->compare('client_id', 'IS NOT NULL');
		$this->getDbCriteria()->mergeWith($crit);
		return $this;
	}
}

Wie man sieht wird in diesen wenigen Zeilen von der Spaltenbezeichnung über die Validationen und Relationen alles geregelt. Zu diesem Zeitpunkt haben Sie noch keine Zeile Code geschrieben. Gii hat all diese Informationen selbst anhand des Datenbank-Schemas abgeleitet und im Code umgesetzt.

Die Methode rules() regelt die Validierung der Formulareingaben. Yii bietet dazu eine Vielzahl an vorgefertigten Validatoren an, welche zusätzlich konfiguriert werden können. Diese Validatoren decken 95% aller Fälle ab.
In relations() werden Model-Beziehungen definiert. Auf Zeile 58 sehen Sie, wie Yii automatisiert eine m:n-Beziehung auflöst. Im Code können Sie über den Aufruf $authors = $book->authors; direkt auf die Autoren zugreifen. Das Schreiben von langen Datenbank-Queries gehört der Vergangenheit an und ist nur noch in Ausnahmesituationen nötig. Der Code bleibt sauber und übersichtlich.
Die Methode attributeLabels() legt lesbare Labels zu den Datenbankspalten fest. Hier kann auch Internationalization (i18n) eingesetzt und die Spaltennamen über Sprachdateien eingelesen werden. Möglichkeiten zur Mehrsprachigkeit bietet Yii ebenfalls vollumfänglich. Zu guter Letzt wird in search() die Suche von Büchern geregelt.

Die Methode scopeIsBorrowed($borrowed=true) habe ich bereits hinzugefügt um die Möglichkeit von Named-Scopes aufzuzeigen. Dies ist eine Möglichkeit, ein Model zu erweitern. Dieser Scope erlaubt es Ihnen nun direkt auf ausgeliehene Bücher zuzugreifen. Book::model()->scopeIsBorrowed()->findAll() liefert nun eine Liste aller ausgeliehenen Bücher. Solche Scopes können auch verknüpft werden.

Controller

Analog zur Generierung des Models können Sie auch den Controller und die Views zu den CRUD-Operationen des Objekts Buch generieren. Yii bietet für die meisten Standard-Aufgaben bereits pfannenfertige Widgets an. Eine tabellarische Auflistung mit Filter-Funktion pro Spalte ist ebenso vorhanden wie Detailansichten, Menus und Formularelemente.

Nachfolgend der Code zum BookController.

<?php

class BookController extends Controller
{

	/**
	* Specifies the access control rules.
	* @return array access control rules
	*/
	public function accessRules()
	{
		return array(
			array('allow',
				'actions'=>array('index','create','view','update','admin'),
				'roles'=>array('authenticated'),
			),
			array('allow',
				'actions'=>array('delete'),
				'roles'=>array('admin'),
			),
			array('deny',
				'users'=>array('*'),
			),
		);
	}

	/**
	* Displays a particular model.
	* @param integer $id the ID of the model to be displayed
	*/
	public function actionView($id)
	{
		$model = $this->loadModel($id);
		$this->render('view', array(
			'model'=>$model,
		));
	}

	/**
	* Creates a new model.
	* If creation is successful, the browser will be redirected to the 'view' page.
	*/
	public function actionCreate()
	{
		$model = new Book();

		if (isset($_POST['Book'])) {
			//load form values into model for validation
			$model->attributes = $_POST['Book'];
			//perform validation and save
			if ($model->save()) {
				$this->redirect(array('view', 'id'=>$model->id));
			}
		}

		$this->render('create', array(
			'model'=>$model,
		));
	}

	/**
	* Updates a particular model.
	* If update is successful, the browser will be redirected to the 'view' page.
	* @param integer $id the ID of the model to be updated
	*/
	public function actionUpdate($id)
	{
		$model = $this->loadModel($id);

		if (isset($_POST['Book'])) {
			$model->attributes=$_POST['Book'];
			if ($model->save()) {
				$this->redirect(array('view', 'id'=>$model->id));
			}
		}

		$this->render('update', array(
			'model'=>$model,
		));
	}

	/**
	* Deletes a particular model.
	* If deletion is successful, the browser will be redirected to the 'admin' page.
	* @param integer $id the ID of the model to be deleted
	*/
	public function actionDelete($id)
	{
		if (Yii::app()->request->isPostRequest) {
			$model = $this->loadModel($id);
			$model->delete();
			if (!isset($_GET['ajax'])) {
				$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));
			}
		} else {
			throw new CHttpException(400, 'Invalid request. Please do not repeat this request again.');
		}
	}

	/**
	* Manages all models.
	*/
	public function actionAdmin()
	{
		//prepare model which holds search criterias
		$model = new Book('search');
		$model->unsetAttributes();

		//load existing search criteria
		if (isset($_GET['Book'])) {
			$model->attributes = $_GET['Book'];
		}

		$this->render('admin', array(
			'model'=>$model,
		));
	}

	/**
	* Returns the data model based on the primary key given in the GET variable.
	* If the data model is not found, an HTTP exception will be raised.
	* @param integer the ID of the model to be loaded
	*/
	public function loadModel($id)
	{
		$model = Book::model()->findByPk($id);
		if ($model === null) {
			throw new CHttpException(404,'The requested page does not exist.');
		}
		return $model;
	}

	/**
	* Performs the AJAX validation.
	* @param CModel the model to be validated
	*/
	protected function performAjaxValidation($model)
	{
		if (isset($_POST['ajax']) && $_POST['ajax']==='book-form') {
			echo CActiveForm::validate($model);
			Yii::app()->end();
		}
	}

}

Yii basiert auf suchmaschinenoptimierte URLs, die vollumfänglich konfigurierbar sind. Jede Browserseite entspricht der Action eines Controllers. Yii setzt das Front-Controller-Pattern ein und leitet so einen Request an den entsprechenden Controller und die zugehörige Action weiter. Ein neues Buch können Sie also über die URL http://www.my-domain.ch/buch/create erfassen. buch ist dabei der Controller und create die zugehörige Action. Der Code dazu finden Sie im vorangehenden Code-Ausschnitt in der Methode actionCreate() auf Zeile 43.

view

Am Schluss einer Controller-Action müssen Sie eine View mit Daten befüllen und ausgeben: $this->render('admin', array('model'=>$model));. Nachfolgend finden Sie den Inhalt der actionAdmin, welche ein Listen-Widget aller Bücher mit Such- und Sortierfunktion ausgibt.

<?php
	$this->widget('zii.widgets.grid.CGridView',array(
		'id'=>'book-grid',
		'dataProvider'=>$model->search(),
		'filter'=>$model,
		'columns'=>array(
			'id',
			'client_id',
			'title',
			array(
				'class'=>'CButtonColumn',
			),
		),
	));
?>

Screenshot

4.Was muss ich denn eigentlich noch tun?

Yii nimmt Ihnen alle grundlegenden Arbeiten ab. Einzig kleine Anpassungen Ihrerseits sind nötig, um den Code dem gewünschten Verhalten anzupassen. Ihre Aufgabe ist es schlussendlich die verschiedenen Objekte miteinander zu verknüpfen. Im vorliegenden Beispiel wäre dies im Formular nötig. Als Benutzer der Software möchten Sie beim ausleihen kaum Kunden-IDs eintragen. Viel angenehmer wäre ein Dropdown mit der Kundenliste zur Auswahl. Auf der Detailseite eines Kunden wäre es ausserdem wünschenswert alle Bücher zu sehen, welche ein Kunde zur Zeit ausgeliehen hat.

Die Hauptarbeit ist also das Umsetzen der Business-Logik und nicht wie bis anhin repetitive Aufgaben. Ausserdem müssen Sie das Design der Applikation Ihren Wünschen anpassen.

5.Wie weiter?

Wenn Sie weitere Informationen oder gar eine Yii-Schulung wünschen, können Sie gerne über das Formular auf unserer Webseite mit uns in Kontakt treten. Allgemeine Informationen zu unserem Angebot im Bereich Web-Applikationen finden Sie auf der Seite Software Engineering im Bereich Web.

Natürlich gibt es auch gute Fachliteratur zum Yii-Framework. Ich empfehlen Ihnen die folgenden Bücher:

6.Schlussfolgerung

Yii ist ein mächtiges Werkzeug ohne dabei aufgeblasen zu wirken. Es werden immer nur die Framework-Teile geladen, welche auch effektiv verwendet werden. Dasselbe gilt für Datenbankzugriffe. Relationen werden - ausser es wird explizit anders verlangt - erst bei Gebrauch aufgelöst. Die Entwicklung mit dem Framework geht sogar so leicht von der Hand, dass Sie es ohne Probleme für Rapid-Prototyping verwenden können.

Durch die einfache Erweiterbarkeit und offene Architektur lässt sich das Framework beliebig um fehlende Funktionalitäten ergänzen. Zudem existiert bereits eine riesige Fan-Gemeinde, welche Extensions zu allen erdenklichen Problemen entwickelt. Besonders empfehlen möchte ich die Bootstrap-Extensions für Yii. Diese bieten einem nicht nur tolle Widgets, sondern auch ein schickes Grundstyling der gesamten Applikation. Die populärsten Bootstrap-Erweiterungen sind Yii-Booster und neu Yiistrap. Eine dieser beiden Erweiterungen sollte bei jeder Yii-Installation eingesetzt werden.

Für das vollständige Verständnis des Frameworks ist eine solide Grundlage der objektorientierten Programmierung unabdingbar. Bringen Sie diese mit, steht Ihrem ersten Yii-Abenteuer nichts mehr im Weg. Probieren Sie es aus!

Auf welche Technologien setzen Sie? Schreiben Sie uns Ihre Meinung in einem Kommentar. Wir sind gespannt!


Pascal Müller

Geschäftsführer


Nach unzähligen Projekten mit Yii kann ich sagen, dass dieses Produkt hält, was es verspricht. Ich empfehle Yii jedem, der eine Web-Applikation entwickeln will, die schnell und gleichzeitig einfach in der Wartung ist. Es eignet sich hervorragend für agile Projektzyklen wie SCRUM und bietet einem Entwickler sämtliche Werkzeuge, die er braucht. Aus diesen Gründen haben wir mitunter auch unsere Website mit Yii umgesetzt.