CakePHP Tutorial: Epsilon-Greedy-Tests - ein Plugin erstellen

CakePHP Tutorial: Epsilon-Greedy-Tests - ein Plugin erstellen
Von Lars Ebert am 15.07.13, 11:00
Kategorien: CakePHP, PHP, Programmieren and Tutorials

Letzte Woche habe ich auf Pixeltuner.de die Epsilon-Greedy-Methode zur Optimierung von Landingpages vorgestellt. Epsilon-Greedy-Tests sind im Grunde modifizierte A/B-Tests. In diesem Artikel möchte ich Dir zeigen, wie Du ein CakePHP-Plugin schreiben kannst. Als Beispiel erstellen wir ein CakePHP-Tutorial, mit dem wir Epsilon-Greedy-Tests durchführen können.

Obwohl ich zwar auf dem Ergebnis der letzten Artikel dieser Serie aufbaue, brauchst Du diese nicht unbedingt gelesen haben, denn das Plugin wird sich automatisch in diese und jede andere CakePHP-App einfügen. Viel wichtiger ist, dass du meinen Artikel über Epsilon-Greedy-Tests gelesen hast, damit du weißt, was ein Epsilon-Greedy-Test ist und wie er funktioniert.

Artikelserie: CakePHP-Tutorial: Web-Anwendung mit MVC

Dieser Artikel ist Teil einer mehrteiligen Artikelserie. Lies dir auch die restlichen Teile durch!

  1. CakePHP Tutorial: Grundfunktionen und Hallo Welt
  2. CakePHP Tutorial: Views, Layouts und Helpers
  3. CakePHP Tutorial - Model und Controller - ein Gästebuch programmieren
  4. CakePHP Tutorial: Admin-Routen und Login - erweitertes Gästebuch
  5. CakePHP Tutorial: Epsilon-Greedy-Tests - ein Plugin erstellen

Schritt 1: Datenbank-Tabelle für die Test anlegen

Als erstes legen wir in unserer Datenbank eine neue Tabelle epsilongreedy_variants an:

SpalteTyp
idbigint(20) unsigned Auto-Inkrement
testvarchar(100)
keyvarchar(100)
viewsbigint(20) unsigned NULL [1]
actionsbigint(20) unsigned NULL [1]

Schritt 2: Plugin anlegen

Als nächstes legen wir im Ordner app/Plugins einen neuen Ordner EpsilonGreedy an. In diesem Ordner befindet sich später unser gesamtes Plugin. In diesem Ordner müssen wir nun noch die Ordner Controller, Model und View an. In diesen können wir später, wie in der regulären App, Controller, Componenten und alle anderen CakePHP-Klassen anlegen.

Im Ordner EpsilonGreedy/Model legen wir nun die Datei EpsilonGreedyAppModel.php an. In dieser können wir für alle Models des Plugins Einstellungen vornehmen, was wir jetzt auch tun.

<?php

	class EpsilonGreedyAppModel extends AppModel
	{
		public $tablePrefix;

		public function __construct($id = false, $table = null, $ds = null) {
			parent::__construct($id, $table, $ds);
			$this->tablePrefix .= 'epsilongreedy_';
		}
	}

?>

Hier legen wir fest, dass alle Models ihren Tabellen den Prefix »epsilongreedy_« hinzufügen. So vermeiden wir, dass wir versehentlich mit einer Tabelle der App kollidieren.

Nun legen wir noch schnell das Model für die Tabelle an, die wir eben angelegt haben.

<?php
	
	class Variant extends EpsilonGreedyAppModel
	{
		
	}
	
?>

Schritt 3: EpsilonGreedyComponent

Als nächstes legen wir die Component an, die sich um die Verwaltung der Epsilon-Greedy-Tests kümmert. Dazu legen wir zuerst den Ordner EpsilonGreedy/Controller/Component an. Hier legen wir nun die Datei EpsilonGreedyComponent.php an.

<?php
	
	class EpsilonGreedyComponent extends Component
	{
		public $tests = array();
		public $explore = .1;

		public function beforeRender($controller) {
			$controller->helpers['EpsilonGreedy.EpsilonGreedy'] = array('explore' => $this->explore);

			$this->Variant = ClassRegistry::init('EpsilonGreedy.Variant');

			foreach($this->tests as $test => $variants) {
				foreach($variants as $key) {
					$variant = $this->Variant->find('first', array(
						'conditions' => array(
							'test' => $test,
							'key' => $key
						)
					));

					if($variant == null) {
						$this->Variant->create();
						$this->Variant->save(array('Variant' => array(
							'test' => $test,
							'key' => $key
						)), false);
					}
				}
			}

			if(isset($controller->request->query['eg'])) {
				$data = unserialize(base64_decode($controller->request->query['eg']));
				$this->success($data['test'], $data['option']);

				$controller->redirect(array());
			}
		}

		public function success($test, $option) {
			$this->Variant->updateAll(
				array(
					'actions' => 'actions+1'
				),
				array(
					'test' => $test,
					'key' => $option
				)
			);
		}
	}
	
?>

Die Component speichert die Varianten in der Datenbank (Zeilen 13 - 30) und sorgt dafür, dass die Erfolge der Tests gezählt werden (Zeilen 32 - 50). Außerdem läd die Component den EpsilonGreedyHelper (Zeile 9), welchen wir als nächstes anlegen.

Schritt 4: EpsilonGreedyHelper

<?php
	
	class EpsilonGreedyHelper extends AppHelper
	{
		public function getOption($test) {
			$this->Variant = ClassRegistry::init('EpsilonGreedy.Variant');

			$clickThroughRates = array();

			$variants = $this->Variant->find('all', array(
				'conditions' => array(
					'test' => $test
				)
			));

			foreach($variants as $variant) {
				if($variant['Variant']['views'] == 0) {
					$clickThroughRates[$variant['Variant']['key']] = 0;
				} else {
					$clickThroughRates[$variant['Variant']['key']] = $variant['Variant']['actions'] / $variant['Variant']['views'];
				}
			}

			if(rand(0, 100) / 100 <= $this->settings['explore']) {
				$choice = array_rand($clickThroughRates);
			} else {
				arsort($clickThroughRates);
				$best = reset($clickThroughRates);
				$winners = array();
				foreach($clickThroughRates as $key => $value) {
					if($value >= $best) {
						$winners[$key] = $value;
					}
				}

				$choice = array_rand($winners);
			}

			$this->Variant->updateAll(
				array(
					'views' => 'views+1'
				),
				array(
					'test' => $test,
					'key' => $choice
				)
			);

			return $choice;
		}

		public function linkParam($test, $choice) {
			return array('eg' => base64_encode(serialize(array('test' => $test, 'option' => $choice))));
		}
	}
	
?>

Der EpsilonGreedyHelper ist für das Frontend der Tests verantwortlich. Hier finden sich die Methoden zur Auswahl einer Variante (Zeilen 5 - 50) und generiert die Link-Parameter, welche zum Zählen der Erfolge benötigt werden (Zeilen 52 - 54).

Schritt 5: Anwendung des Plugins

Das Plugin funktiniert jetzt schon, wir müssen es nur noch anwenden. Dazu laden wir es zunächst über die Datei Config/bootstrap.php:

CakePlugin::load('EpsilonGreedy');

Nun müssen wir die Component nur noch in den AppController laden:

<?php
App::uses('Controller', 'Controller');

class AppController extends Controller {
	public $helpers = array('Nav');
	public $components = array(
		'Session',
		'Auth' => array(
			'loginRedirect' => array('controller' => 'entries', 'action' => 'index', 'admin' => true),
			'logoutRedirect' => array('controller' => 'pages', 'action' => 'display', 'home', 'admin' => false),
			'loginAction' => array('controller' => 'users', 'action' => 'login', 'admin' => false),
			'authError' => 'Melde dich an, um diesen Bereich zu sehen!'
		),
		'EpsilonGreedy.EpsilonGreedy' => array(
			'tests' => array(
				'landingpage' => array(
					'a', 'b', 'c'
				)
			)
		)
	);
	
	public function beforeFilter() {
		if(!empty($this->params['prefix']) && $this->params['prefix'] == 'admin'){
			$this->Auth->deny();
         } else {
			$this->Auth->allow();
		}
	}
}

Über das Array tests können wir die Epsilon-Greedy-Test einrichten. Hier pflegen wir in diesem Fall einen Test mit dem Titel »landingpage« ein. Die drei Varianten heißen hier a, b und c. Hier können beliebig viele Test und Varianten eingepflegt werden.

Als letztes müssen wir nur noch den Epsilon-Greedy-Test durchführen. Das können wir sehr einfach in jedem beliebigen View durchführen:

<?php
	
	$test = 'landingpage';
	$choice = $this->EpsilonGreedy->getOption($test);

?>

<h1>Landingpage</h1>
<?php echo $this->Html->link('Variante ' . $choice, array('controller' => 'pages', 'action' => 'display', 'home', '?' => $this->EpsilonGreedy->linkParam($test, $choice))); ?>

Über die Methode getOption können wir eine Variante auswählen. Dann fügen wir den Link mit den entsprechenden Parametern ein. Hier sollten nun natürlich verschiedene Varianten des gleichen Contents angezeigt werden, um zu prüfen, welche Variante am effektivsten ist.

Und schon haben wir ein Plugin, um Epsilon-Greedy-Test durchzuführen. Ich hoffe, dir hat das Tutorial gefallen und alles hat funktioniert. Also schreib mir einen Kommentar, ich freue mich auf Dein Feedback!