<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Let them eat cake</title>
	<atom:link href="http://cake.ferdinand-keil.de/feed/" rel="self" type="application/rss+xml" />
	<link>http://cake.ferdinand-keil.de</link>
	<description>Abenteuer mit CakePHP</description>
	<lastBuildDate>Mon, 20 Jun 2011 12:06:03 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>SearchComponent für fehlertolerante Suche</title>
		<link>http://cake.ferdinand-keil.de/2011/06/20/searchcomponent-fur-fehlertolerante-suche/</link>
		<comments>http://cake.ferdinand-keil.de/2011/06/20/searchcomponent-fur-fehlertolerante-suche/#comments</comments>
		<pubDate>Mon, 20 Jun 2011 12:04:30 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Komponenten]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[component]]></category>
		<category><![CDATA[controller]]></category>
		<category><![CDATA[mit license]]></category>
		<category><![CDATA[search]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=65</guid>
		<description><![CDATA[Nachdem ich sowieso eine Suchfunktion für mehrere Tabellen brauchte, kam mir die Idee diese gleich mit einer Fehlerkorrektur zu implementieren. Herausgekommen ist die unten aufgeführte Komponente. Sie kann im Controller wie folgt aufgerufen werden: Im View stehen dann die folgenden Variablen zur Verfügung: $people gefundene Einträge &#8211; wurden mit paginate gesucht $searchString der ursprüngliche Suchstring [...]]]></description>
			<content:encoded><![CDATA[<p>Nachdem ich sowieso eine Suchfunktion für mehrere Tabellen brauchte, kam mir die Idee diese gleich mit einer Fehlerkorrektur zu implementieren. Herausgekommen ist die unten aufgeführte Komponente. Sie kann im Controller wie folgt aufgerufen werden:</p>
<pre class="brush: php; title: ; notranslate">
// im Kopf des Controllers
var $components = array(
	'Search' =&gt; array(
		'model' =&gt; 'Person',
		'fields' =&gt; array('surname', 'given_names')
	)
);

/* ... */

function search() {
	/* Suchstring abfragen und von ungültigen Zeichen befreien ...  */

	if (($search = $this-&gt;Search-&gt;search($searchString)) !== false) {
		$this-&gt;set($search);
	}

}
</pre>
<p>Im View stehen dann die folgenden Variablen zur Verfügung:</p>
<ul>
<li><code>$people</code> gefundene Einträge &#8211; wurden mit paginate gesucht</li>
<li><code>$searchString</code> der ursprüngliche Suchstring</li>
<li><code>$didYouMean</code> die Verbesserung des ursprünglichen Strings</li>
<li><code>$newSearchString</code> der neue Suchstring</li>
</ul>
<p>Der Name der Variable mit den Einträgen wird entweder aus dem Wert für <code>$model</code> hergeleitet oder kann mit <code>$table</code> direkt angegeben werden.<br />
Alle weiteren Fragen sollten sich aus dem Code beantworten.</p>
<p>Datei <em>search.php</em>:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * SearchComponent
 * Stellt eine fehlertolerante Suche zur Verfügung.
 *
 * @author ferdinand
 * @license MIT
 *
 * Copyright (c) 2011 Ferdinand Keil
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the &quot;Software&quot;), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,´
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * ncluded in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
class SearchComponent extends Object {

	var $name = 'Search';

	/*
	 * Model, das die Suche verwenden soll
	 */
	var $model = '';

	/*
	 * Name der Tabelle für die Rückgabe (optional)
	 */
	var $table = '';

	/*
	 * Felder die für Vorschläge durchsucht werden sollen
	 */
	var $fields = array();

	/*
	 * Controller
	 */
	var $controller = null;

	/*
	 * Cache mit allen Einträgen
	 */
	var $_allEntries = array();

	/*
	 * Maximale Anzahl Begriffe die verbessert werden
	 */
	var $_limit = 2;

	/*
	 * Maximale Längendifferenz zwischen Strings bei der Suche nach Alternativen
	 */
	var $_maxLenDiff = 4;

	/*
	 * Minimale Länge für Strings bei der Suche nach Alternativen
	 */
	var $_minLen = 3;

	/*
	 * Maximale Levenshtein Distanz zwischen String bei der Suche nach Alternativen
	 */
	var $_maxLevDist = 3;

	/**
	 * Initialisiert die Component
	 *
	 * @param $controller Controller
	 * @param $settings Einstellungen
	 */
	function initialize(&amp;$controller, $settings) {
		foreach ($settings as $name =&gt; $value) {
			if (empty($this-&gt;{$name})) {
				$this-&gt;{$name} = $value;
			}
		}
		if (empty($this-&gt;table)) {
			$this-&gt;table = Inflector::tableize($this-&gt;model);
		}
		$this-&gt;controller =&amp; $controller;
	}

	/**
	 * Fehlertolerante Such Funktion
	 * Tippfehler werden toleriert und ein alternativer Suchstring
	 * erzeugt. Auf dessen Basis wird die Suche erneut durchgeführt.
	 *
	 * @return array
	 */
	function search($searchString = '') {
		if (empty($searchString)) {
			return false;
		}

		$searchData = explode(' ', $searchString);
		$conditions = array();
		foreach ($searchData as $entry) {
			$c = array();
			foreach ($this-&gt;fields as $field) {
				$c[&quot;{$this-&gt;model}.{$field} LIKE&quot;] = &quot;%{$entry}%&quot;;
			}
			$conditions['AND'][] = array(
				'OR' =&gt; $c
			);
		}

		${$this-&gt;table} = array();
		$didYouMean = '';
		$newSearchString = '';
		$modifiedSearchData = array();

		// Schritt 1:
		// wenn kein Eintrag mit dem Suchstring gefunden wird,
		// Alternativen für die Suchbegriffe suchen
		if ($this-&gt;controller-&gt;{$this-&gt;model}-&gt;find('count', array('conditions' =&gt; $conditions)) == 0) {
			// Schritt 1a:
			// Array mit Begriffen durchlaufen
			for ($i = 0; $i &lt; min($this-&gt;_limit, count($searchData)); $i++) {
				$currString = $searchData[$i];
				$conditions = array('OR' =&gt; array());
				foreach ($this-&gt;fields as $field) {
					$conditions['OR'][&quot;{$this-&gt;model}.{$field} LIKE&quot;] = &quot;%{$currString}%&quot;;
				}
				$proposal = '';
				// Schritt 2:
				// prüfen ob die Anfragen an diesem Begriff scheitert
				if ($this-&gt;controller-&gt;{$this-&gt;model}-&gt;find('count', array('conditions' =&gt; $conditions)) == 0) {
					// Schritt 2a:
					// wenn ja, Alternative für den Begriff suchen
					$proposal = $this-&gt;_findProposal($currString);
					if (!empty($proposal)) {
						// wenn Alternative gefunden wurde, diese verwenden und
						// weitere Suche abbrechen
						$modifiedSearchData[] = $proposal;
						$modifiedSearchData = array_merge($modifiedSearchData, array_slice($searchData, $i+1));
						$didYouMean .= &quot;&lt;b&gt;{$proposal}&lt;/b&gt; &quot;. implode(' ', array_slice($searchData, $i+1));
						break;
					} else {
						// wenn keine Alternative gefunden wurde, Begriff
						// durchstreichen
						$didYouMean .= &quot;&lt;del&gt;{$currString}&lt;/del&gt; &quot;;
					}
				} else {
					// Schritt 2b:
					// wenn Abfrage nicht an dem Begriff scheitert ihn
					// weiter verwenden
					$modifiedSearchData[] = $currString;
					$didYouMean .= &quot;{$currString} &quot;;
				}
			}
			// Schritt 3:
			// prüfen ob eine funktionierende Abfrage zusammengekommen ist
			if (!empty($modifiedSearchData)) {
				// Schritt 3a:
				// Abfrage ist ok, Daten abfragen
				$conditions = array();
				foreach ($modifiedSearchData as $entry) {
					$c = array();
					foreach ($this-&gt;fields as $field) {
						$c[&quot;{$this-&gt;model}.{$field} LIKE&quot;] = &quot;%{$entry}%&quot;;
					}
					$conditions['AND'][] = array(
						'OR' =&gt; $c
					);
				}
				$this-&gt;{$this-&gt;model}-&gt;recursive = 0;
				${$this-&gt;table} = $this-&gt;controller-&gt;paginate($this-&gt;model, $conditions);
				$newSearchString = implode(' ', $modifiedSearchData);
				if (count($searchData) &gt; count($modifiedSearchData)) {
					$didYouMean .= '&lt;del&gt;'. implode('&lt;/del&gt; &lt;del&gt;', array_slice($searchData, $this-&gt;_limit)) .'&lt;/del&gt;';
				}
				$didYouMean = trim($didYouMean);

			} else {
				// Schritt 3b:
				// Abfrage ist nicht ok, Vorschläge verwerfen
				$didYouMean = '';
				$modifiedSearchData = array();
				// paginate muss einmal aufgerufen werden
				${$this-&gt;table} = $this-&gt;controller-&gt;paginate($this-&gt;model, array('0 = 1'));
			}
		} else {
			// Schritt 1b:
			// Abfrage ausführen
			$this-&gt;{$this-&gt;model}-&gt;recursive = 0;
			${$this-&gt;table} = $this-&gt;controller-&gt;paginate($this-&gt;model, $conditions);
		}

		// Variablen zurückgeben
		return compact($this-&gt;table, 'searchString', 'didYouMean', 'newSearchString');
	}

	/**
	 * Suche nach Alternativbegriff
	 * Durchsucht die Datenbank nach einem alternativen Begriff.
	 *
	 * @param $needle Suchbegriff
	 * @return String
	 */
	function _findProposal($needle = null) {
		if (empty($needle)) {
			return '';
		}

		$proposal = '';

		// Schritt 1:
		// Für das Wort aus dem Such-String in der Datenbank nach Alternativen suchen.
		// Caching
		if (empty($this-&gt;_allEntries)) {
			$this-&gt;_allEntries = $this-&gt;controller-&gt;{$this-&gt;model}-&gt;find('all', array('fields' =&gt; $this-&gt;fields, 'recursive' =&gt; -1));
		}
		$proposals = array();
		$umlaute = array('ä' =&gt; 'ae', 'ö' =&gt; 'oe', 'ü' =&gt; 'ue', 'ß' =&gt; 'ss');
		foreach ($this-&gt;_allEntries as $entry) {
			foreach ($entry[$this-&gt;model] as $field) {
				if (!empty($field)) {
					// alle nicht Buchstaben Zeichen durch Leerzeichen ersetzen
					$field = preg_replace('/[^a-zöäüß]/i', ' ', $field);
					$words = explode(' ', $field);
					foreach ($words as $word) {
						// Word ignorieren falls
						// * gefundenes und gesuchtes Wort gleich sind (nicht plausibel)
						// * gefundenes Wort kürzer als 3 Zeichen ist
						// * die Wörter um mehr als 4 Zeichen in der Länge abweichen
						if (($word != $needle) &amp;&amp; (strlen($word) &gt; $this-&gt;_minLen) &amp;&amp; (strlen($word) - strlen($needle) &lt;= $this-&gt;_maxLenDiff)) {
							$levenshteinDist = levenshtein(strtr($needle, $umlaute), strtr($word, $umlaute));
							if (($levenshteinDist &lt;= $this-&gt;_maxLevDist) &amp;&amp; !isset($proposals[$word])) {
								$proposals[$word] = $levenshteinDist;
							}
						}
					}
				}
			}
		}
		// Schritt 2:
		// Wenn eine Alternative gefunden wurde, diese verwenden, sonst die
		// beste alternative anhand eines phonetischen Algorithmus suchen.
		if (count($proposals) == 1) {
			$proposal = current(array_keys($proposals));
		} elseif (count($proposals) &gt; 1) {
			$best = '';
			foreach (array_keys($proposals) as $word) {
				if (levenshtein(soundex(strtr($needle, $umlaute)), soundex(strtr($word, $umlaute))) &lt; levenshtein(soundex(strtr($needle, $umlaute)), soundex(strtr($best, $umlaute)))) {
					$best = $word;
				}
			}
			$proposal = $best;
		}

		// Vorschlag zurückgeben
		return $proposal;
	}

}

?&gt;
</pre>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2011/06/20/searchcomponent-fur-fehlertolerante-suche/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>date und die Zeitumstellung</title>
		<link>http://cake.ferdinand-keil.de/2010/11/15/date-und-die-zeitumstellung/</link>
		<comments>http://cake.ferdinand-keil.de/2010/11/15/date-und-die-zeitumstellung/#comments</comments>
		<pubDate>Sun, 14 Nov 2010 23:31:35 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Allgemein]]></category>
		<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=61</guid>
		<description><![CDATA[Ich hatte folgendes Problem: ich will einen Kalender ausgeben. Dazu bestimme ich den ersten Tag des Monats und laufe dann Woche für Woche durch. Die date Funktion liefert dazu alle nötigen Informationen: Anzahl der Tage in einem Monat, Tag des Monats und den Wochentag als Zahl. Der Code sieht so ähnlich aus: Das funktioniert soweit [...]]]></description>
			<content:encoded><![CDATA[<p>Ich hatte folgendes Problem: ich will einen Kalender ausgeben. Dazu bestimme ich den ersten Tag des Monats und laufe dann Woche für Woche durch. Die <a href="http://de.php.net/manual/de/function.date.php">date</a> Funktion liefert dazu alle nötigen Informationen: Anzahl der Tage in einem Monat, Tag des Monats und den Wochentag als Zahl. Der Code sieht so ähnlich aus:</p>
<pre class="brush: php; title: ; notranslate">
$startTime = mktime(0, 0, 0, $month, $day, $year);
$endTime = ...;

$curWeek = $startTime;
while (date('m', $curWeek) == date('m', $endTime) {
	$timeStep = min(
		8 - date('N', $curWeek), // eine Woche vor
		date('t', $curWeek) - date('j', $curWeek) + 1 // Ende des Monats
	);
	$nextWeek = $curWeek + 24 * 3600 * $timeStep;

	...
}
</pre>
<p>Das funktioniert soweit auch wunderbar, nur im Oktober gibt es ein Problem. Dank der Zeitumstellung ist der nämlich genau eine Stunde länger. Das berücksichtigt die Variable <em>timeStep</em> natürlich nicht. Die Alternative ist die Verwendung von <a href="http://de.php.net/manual/de/function.strtotime.php">strtotime</a>. Beispiel:</p>
<pre class="brush: php; title: ; notranslate">
$startTime = mktime(0, 0, 0, $month, $day, $year);
$endTime = ...;

$curWeek = $startTime;
while (date('m', $curWeek) == date('m', $endTime) {
	$timeStep = min(
		8 - date('N', $curWeek), // eine Woche vor
		date('t', $curWeek) - date('j', $curWeek) + 1 // Ende des Monats
	);
	$nextWeek = strtotime(&quot;+{$timeStep} day&quot;, $curWeek);

	...
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/11/15/date-und-die-zeitumstellung/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>GROUP BY HAVING Statements</title>
		<link>http://cake.ferdinand-keil.de/2010/11/11/having-und-group-by-statements/</link>
		<comments>http://cake.ferdinand-keil.de/2010/11/11/having-und-group-by-statements/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 16:07:24 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Datenbanken]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=24</guid>
		<description><![CDATA[Um GROUP BY HAVING Statements zu formulieren ist in CakePHP bis Version 1.3 leider noch ein Hack erforderlich (ob HAVING Teil von Version 2 wird weiß ich allerdings nicht). Ein SQL GROUP BY kann einfach durch den Parameter group bei einem beliebigen Query angegeben werden (3.7.3.1 find). HAVING kann dabei wie folgt verwendet werden: Dieser [...]]]></description>
			<content:encoded><![CDATA[<p>Um <em>GROUP BY HAVING</em> Statements zu formulieren ist in CakePHP bis Version 1.3 leider noch ein Hack erforderlich (ob <em>HAVING</em> Teil von Version 2 wird weiß ich allerdings nicht). Ein SQL <em>GROUP BY</em> kann einfach durch den Parameter <em>group</em> bei einem beliebigen Query angegeben werden (<a href="http://book.cakephp.org/view/1018/find">3.7.3.1 find</a>). <em>HAVING</em> kann dabei wie folgt verwendet werden:</p>
<pre class="brush: php; title: ; notranslate">
$result = $this-&gt;Model-&gt;find('all', array(
	'group' =&gt; array('Model.parameter HAVING COUNT(*) &gt;= 3'),
	... // weitere Optionen
};
</pre>
<p>Dieser Aufruf resultiert in folgendem Query:</p>
<pre class="brush: sql; title: ; notranslate">
SELECT `Model`.* FROM `model` AS `Model` GROUP BY `Model`.`parameter` HAVING COUNT(*) &gt;= 3
</pre>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/11/11/having-und-group-by-statements/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Model Callbacks und HABTM Beziehungen</title>
		<link>http://cake.ferdinand-keil.de/2010/11/11/model-callbacks-und-habtm-beziehungen/</link>
		<comments>http://cake.ferdinand-keil.de/2010/11/11/model-callbacks-und-habtm-beziehungen/#comments</comments>
		<pubDate>Thu, 11 Nov 2010 13:56:39 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Allgemein]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=49</guid>
		<description><![CDATA[Die Callback Funktionen im Model ermöglichen es Daten vor und nach dem Abfragen oder Abspeichern zu verändern (CakePHP Book: 3.7.7 Callback Methods). Das kann zum Beispiel nützlich sein um Ausgaben für einen Benutzer je nach Rolle anzupassen. Die Daten werden erst gar nicht abgefragt indem das Query vorher entsprechend verändert wird. Somit wird auf unterster [...]]]></description>
			<content:encoded><![CDATA[<p>Die Callback Funktionen im Model ermöglichen es Daten vor und nach dem Abfragen oder Abspeichern zu verändern (<a href="http://book.cakephp.org/view/1048/Callback-Methods">CakePHP Book: 3.7.7 Callback Methods</a>). Das kann zum Beispiel nützlich sein um Ausgaben für einen Benutzer je nach Rolle anzupassen. Die Daten werden erst gar nicht abgefragt indem das Query vorher entsprechend verändert wird. Somit wird auf unterster Ebene sichergestellt, dass jeder nur die Daten sieht die er sehen darf.<br />
Callbacks funktionieren allerdings nicht, wenn die Daten über eine has and belongs to many Relation abgerufen werden.</p>
<p>Beispiel:<br />
Folgende Datenbank sei gegeben<br />
<code><br />
+--------+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+-------------------+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+-------------+<br />
| people | <--> | department_people | <--> | departments |<br />
+--------+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+-------------------+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+-------------+<br />
</code></p>
<p><em>department_people</em> verbindet <em>people</em> und <em>department</em> über eine HABTM Relation.</p>
<p>Model <em>person.php</em>:</p>
<pre class="brush: php; title: ; notranslate">
class Person extends AppModel {

	var $name = 'Person';

	// beforeFind Callback
	function beforeFind($queryData) {
		echo 'beforeFind';
		$queryData = ... // queryData anpassen;
		return $queryData;
	}

	// find überschreiben
	function find($conditions = null, $fields = array(), $order = null, $recursive = null) {
		echo 'find';
		return parent::find($conditions, $fields, $order, $recursive);
	}

}
</pre>
<p>Controller <em>departments_people_controller.php</em>:</p>
<pre class="brush: php; title: ; notranslate">
class DepartmentsPeopleController extends AppController {

	var $name = 'DepartmentsPeople';

	function index() {
		$res = $this-&gt;DepartmentsPeople-&gt;find('all');
		// Gibt beforeFind und find aus.
		$this-&gt;set(compact('res'));
	}

}
</pre>
<p>Controller <em>departments_controller.php</em>:</p>
<pre class="brush: php; title: ; notranslate">
class DepartmentsController extends AppController {

	var $name = 'Departments';

	function index() {
		$res = $this-&gt;Departments-&gt;find('all');
		// Keine Ausgabe
		$this-&gt;set(compact('res'));
	}

}
</pre>
<p>Das Problem ist also, dass weder das Callback <em>beforeFind</em> noch die überschriebene Methode <em>find</em> aufgerufen werden, wenn eine HABTM Verknüpfung vorliegt. Die Folge ist, dass die Änderungen an <em>queryData</em> folgenlos bleiben. Es gibt allerdings eine Möglichkeit auf die Abfrage Einfluss zu nehmen: der <em>conditions</em> Parameter im Model <em>department</em>.</p>
<p>Model <em>department.php</em>:</p>
<pre class="brush: php; title: ; notranslate">
class Department extends AppModel {

	var $name = 'Department';

	var $hasAndBelongsToMany = array(
			'Person' =&gt; array(
						'className' =&gt; 'Person',
						'joinTable' =&gt; 'departments_people',
						'conditions' =&gt; array( ... ) // beliebige SQL Befehle als Bedingung für die Abfrage; auch Substatements
			)
	);

}
</pre>
<p>Allerdings muss diese Bedingung in jedes Model das mit <em>Person</em> über eine HABTM Relation verknüpft ist eingefügt werden.</p>
<p>Warum CakePHP allerdings das Model bei der Abfrage der Verknüpften Modelle nicht berücksichtigt ist mir ein Rätsel. Möglicherweise aus Performanz Gründen.</p>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/11/11/model-callbacks-und-habtm-beziehungen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>UPDATE statt INSERT</title>
		<link>http://cake.ferdinand-keil.de/2010/04/28/update-statt-insert/</link>
		<comments>http://cake.ferdinand-keil.de/2010/04/28/update-statt-insert/#comments</comments>
		<pubDate>Wed, 28 Apr 2010 15:27:36 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Datenbanken]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=37</guid>
		<description><![CDATA[Im Sinne von Fat Models &#8211; Skinny Controllers wollte ich folgendes Problem im Model lösen: bevor ein Eintrag gespeichert wird, wird die Datenbank nach einem Eintrag mit gleichem Inhalt durchsucht und gegebenenfalls dieser verändert. Ich ging davon aus, dass diese Aufgabe am besten im beforeSave() Callback aufgehoben sei. Cake entscheidet anhand des Felds Model->id ob [...]]]></description>
			<content:encoded><![CDATA[<p>Im Sinne von <a href="http://gluei.com/blog/view/cakephp-best-practices-fat-models-and-skinny-controllers">Fat Models &#8211; Skinny Controllers</a> wollte ich folgendes Problem im Model lösen:<br />
bevor ein Eintrag gespeichert wird, wird die Datenbank nach einem Eintrag mit gleichem Inhalt durchsucht und gegebenenfalls dieser verändert.</p>
<p>Ich ging davon aus, dass diese Aufgabe am besten im <em>beforeSave()</em> Callback aufgehoben sei. Cake entscheidet anhand des Felds <em>Model->id</em> ob ein Eintrag erstellt oder verändert werden soll. Also muss man nur die Id des bereits vorhandenen Eintrags suchen und das Feld entsprechend setzen.<br />
Das funktioniert allerdings nicht &#8211; Cake erstellt trotzdem stur neue Einträge. Eine Google Suche brachte die <a href="http://marianoiglesias.com.ar/cakephp/cakephp-tip-of-the-day-pay-attention-to-conventions/">Lösung</a>. Cake prüft die Existenz von Einträgen während des Validierens. Der richtige Callback für solche Aufgaben ist also <strong><em>beforeValidate()</em></strong>.</p>
<p>Mit folgendem Code funktioniert das jetzt einwandfrei:</p>
<pre class="brush: php; title: ; notranslate">
function beforeValidate() {
	if (isset($this-&gt;data['Difference']) &amp;&amp; !empty($this-&gt;data['Difference'])) {
		// conditions anpassen
		$conditions = $this-&gt;data['Difference'];
		unset($conditions['value_a'], $conditions['value_b']);
		// nach bereits existierendem Eintrag suchen
		$entry = $this-&gt;find('first', array(
			'conditions' =&gt; $conditions,
			'fields' =&gt; array('id', 'flag')
		));
		// falls Eintrag schon existiert UPDATE,
		// sonst INSERT
		if (!empty($entry)) {
			$this-&gt;id = $entry['Difference']['id'];
			$this-&gt;data['Difference']['flag'] = $entry['Difference']['flag'];
		} else {
			$this-&gt;data['Difference']['flag'] = 'new';
		}
		return true;
	}
	return false;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/04/28/update-statt-insert/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Probleme mit der Wizard Component</title>
		<link>http://cake.ferdinand-keil.de/2010/04/26/probleme-mit-der-wizard-component/</link>
		<comments>http://cake.ferdinand-keil.de/2010/04/26/probleme-mit-der-wizard-component/#comments</comments>
		<pubDate>Mon, 26 Apr 2010 13:26:59 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Komponenten]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=29</guid>
		<description><![CDATA[Ich nutze für die Anwendung an der ich arbeite die Wizard Component mit der sich Formulare über mehrere Seiten verteilen lassen. Das Tutorial schlägt folgenden Code für den View vor um das Formular auf den Wizard verweisen zu lassen: Das funktioniert allerdings nur so lange, wie CakePHP im Wurzelverzeichnis eines Webservers abgelegt ist. Liegt die [...]]]></description>
			<content:encoded><![CDATA[<p>Ich nutze für die Anwendung an der ich arbeite die <a href="http://bakery.cakephp.org/articles/view/wizard-component-1-2-1">Wizard Component</a> mit der sich Formulare über mehrere Seiten verteilen lassen.<br />
Das <a href="http://bakery.cakephp.org/articles/view/wizard-component-1-2-tutorial">Tutorial</a> schlägt folgenden Code für den View vor um das Formular auf den Wizard verweisen zu lassen:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?=$form-&gt;create('Signup', array('id' =&gt; 'SignupForm', 'url' =&gt; $this-&gt;here));?&gt;
...
&lt;?=$form-&gt;end();?&gt;
</pre>
<p>Das funktioniert allerdings nur so lange, wie CakePHP im Wurzelverzeichnis eines Webservers abgelegt ist. Liegt die Installtion beispielsweise im Verzeichnis <em>foobar</em> erzeugt der Code oben eine Formular mit dem Ziel <em>/foobar/<strong>foobar/</strong>controller/wizard/step</em>. Cake interpretiert das als Aktion <em>foobar</em> des Controllers <em>foobar</em> und zielt damit ins Leere.<br />
Um diesen Fehler zu umgehen sollte die URL wie auch sonst bei CakePHP als Array angegeben werden:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?=$form-&gt;create('Signup', array('id' =&gt; 'SignupForm', 'url' =&gt; array('controller' =&gt; 'controller', 'action' =&gt; 'wizard', 'signup')));?&gt;
...
&lt;?=$form-&gt;end();?&gt;
</pre>
<p>Dabei ist <em>controller</em> der Controller der die Komponente enthält und <em>signup</em> der aktuelle Schritt.</p>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/04/26/probleme-mit-der-wizard-component/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Transaktionen unter CakePHP 1.2.x</title>
		<link>http://cake.ferdinand-keil.de/2010/03/30/transaktionen-unter-cakephp-1-2-x/</link>
		<comments>http://cake.ferdinand-keil.de/2010/03/30/transaktionen-unter-cakephp-1-2-x/#comments</comments>
		<pubDate>Tue, 30 Mar 2010 16:39:03 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Datenbanken]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=10</guid>
		<description><![CDATA[Für einen Wizard der eine Reihe von Einträgen aus einer CSV Datei in die Datenbank importiert wollte ich die INSERT-Statements in einer Transaktion vereinen. An verschiedenen Stellen (z.B. hier) liest man, das würde mit funktionieren. Ich hab alles versucht, aber so lies sich das nicht zum laufen bringen. Den entscheidenden Hinweis hab ich dann im [...]]]></description>
			<content:encoded><![CDATA[<p>Für einen Wizard der eine Reihe von Einträgen aus einer CSV Datei in die Datenbank importiert wollte ich die INSERT-Statements in einer Transaktion vereinen. An verschiedenen Stellen (z.B. <a href="http://www.blog.cakephp4all.com/?p=7">hier</a>) liest man, das würde mit</p>
<pre class="brush: php; title: ; notranslate">
$this-&gt;Model-&gt;transactional = true;
$this-&gt;Model-&gt;begin();
...
$this-&gt;Model-&gt;commit();
</pre>
<p>funktionieren. Ich hab alles versucht, aber so lies sich das nicht zum laufen bringen.</p>
<p>Den entscheidenden Hinweis hab ich dann im <a href="http://cakephp.lighthouseapp.com/projects/42648/tickets/485-begin-commit-rollback-doesnt-work-on-mssql">CakePHP Bugtracker</a> gefunden. Mit</p>
<pre class="brush: php; title: ; notranslate">$this-&gt;Model-&gt;getDataSource()-&gt;begin($this-&gt;Model);</pre>
<p>wird jetzt endlich eine Transaktion gestartet. Den Parameter <em>transactional</em> ignoriert CakePHP dabei allerdings, es funktioniert mit und ohne.</p>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2010/03/30/transaktionen-unter-cakephp-1-2-x/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>$this-&gt;set(&#8216;foobar&#8217;, &#8216;Hallo Welt!&#8217;);</title>
		<link>http://cake.ferdinand-keil.de/2009/11/03/hallo-welt/</link>
		<comments>http://cake.ferdinand-keil.de/2009/11/03/hallo-welt/#comments</comments>
		<pubDate>Tue, 03 Nov 2009 15:51:19 +0000</pubDate>
		<dc:creator>ferdinand</dc:creator>
				<category><![CDATA[Allgemein]]></category>

		<guid isPermaLink="false">http://cake.ferdinand-keil.de/?p=1</guid>
		<description><![CDATA[Ich habe vor etwas mehr als einem Jahr angefangen an einem kleinen Projekt für die Universität Würzburg zu entwickeln. Da ich bei vorigen Projekten gemerkt hatte wie anstrengend es sein kann jedes Mal das Rad neu erfinden zu müssen habe ich mich dafür entschieden ein Framework zu verwenden. Unter allen PHP-Frameworks gefiel mir dabei CakePHP [...]]]></description>
			<content:encoded><![CDATA[<p>Ich habe vor etwas mehr als einem Jahr angefangen an einem kleinen Projekt für die Universität Würzburg zu entwickeln. Da ich bei vorigen Projekten gemerkt hatte wie anstrengend es sein kann jedes Mal das Rad neu erfinden zu müssen habe ich mich dafür entschieden ein Framework zu verwenden. Unter allen PHP-Frameworks gefiel mir dabei CakePHP am besten (CodeIgniter kam auf den zweiten Platz). Die &#8220;Magie&#8221; von CakePHP ist einfach genau meins <img src='http://cake.ferdinand-keil.de/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .<br />
Da ich schon bei einigen Problemen mit CakePHP lange suchen musste bis ich die Lösung gefunden habe, dachte ich mir, ich schreibe doch selber mal was über Fallstricke und Tricks in CakePHP. Ich hoffe ich komme regelmäßig dazu hier etwas neues zu veröffentlichen. Man darf gespannt bleiben.</p>
]]></content:encoded>
			<wfw:commentRss>http://cake.ferdinand-keil.de/2009/11/03/hallo-welt/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

