Skip to content

Instantly share code, notes, and snippets.

@s3b4stian
Last active November 5, 2017 22:10
Show Gist options
  • Save s3b4stian/09c303576cea9197b547a27684ea4421 to your computer and use it in GitHub Desktop.
Save s3b4stian/09c303576cea9197b547a27684ea4421 to your computer and use it in GitHub Desktop.
Model View Controller Example
<?php
/**
* Parent class for custom model classes.
*
* This class was implemented like part of Observer pattern
* https://en.wikipedia.org/wiki/Observer_pattern
* http://php.net/manual/en/class.splsubject.php
*/
class Model implements SplSubject {
/**
* @var object List of attached observerer
*/
private $observers;
/**
* @var array Data for notify to observerer
*/
private $updates = [];
/**
* Class Constructor.
*/
public function __construct() {
$this->observers = new SplObjectStorage();
}
/**
* Attach an Observer class to this Subject for updates
* when occour a subject state change.
*
* @param SplObserver $observer
*/
public function attach(SplObserver $observer) {
if ($observer instanceof View) {
$this->observers->attach($observer);
}
}
/**
* Detach an Observer class from this Subject.
*
* @param SplObserver $observer
*/
public function detach(SplObserver $observer) {
if ($observer instanceof View) {
$this->observers->detach($observer);
}
}
/**
* Notify a state change of Subject to all registered Observeres.
*/
public function notify() {
foreach ($this->observers as $value) {
$value->update($this);
}
}
/**
* Set the data to notify to all registered Observeres.
*
* @param array $data
*/
public function set(array $data) {
$this->updates = array_merge_recursive($this->updates, $data);
}
/**
* Get the data to notify to all registered Observeres.
*
* @return array
*/
public function get(): array {
return $this->updates;
}
}
/**
* Parent class for custom view classes.
*
* This class was implemented like part of Observer pattern
* https://en.wikipedia.org/wiki/Observer_pattern
* http://php.net/manual/en/class.splobserver.php
*/
class View implements SplObserver {
/**
* @var array Data for the dynamic view
*/
protected $data = [];
/**
* @var string Output data
*/
protected $output = '';
/**
* @var Model Model for access data
*/
protected $model;
/**
* Class Constructor.
*
* @param Model $model
*/
public function __construct(Model $model) {
$this->model = $model;
}
/**
* Render a template.
*/
public function render(): string {
return $this->output;
}
/**
* Update Observer data.
*
* @param SplSubject $subject
*/
public function update(SplSubject $subject) {
if ($subject instanceof Model) {
$this->data = array_merge($this->data, $subject->get());
}
}
}
/**
* Parent class for custom controller classes.
*/
class Controller {
/**
* @var object The model object for current controller
*/
protected $model = null;
/**
* Class Constructor.
*
* @param object $model
*/
public function __construct(Model $model) {
$this->model = $model;
}
}
/**
* Our Custom Model
*/
class CalculatorModel extends Model {
/**
* Class Constructor.
*/
public function __construct() {
parent::__construct();
}
/**
* Mutiply business logic.
*
* @param array $numbers
*/
public function multiply(array $numbers) {
$this->set([
'result' => $this->operation('*', $numbers),
'operands' => $numbers,
]);
}
/**
* Divide business logic.
*
* @param array $numbers
*/
public function divide(array $numbers) {
$this->set([
'result' => $this->operation('/', $numbers),
'operands' => $numbers
]);
}
/**
* Subtraction business logic.
*
* @param array $numbers
*/
public function sub(array $numbers) {
$this->set([
'result' => $this->operation('-', $numbers),
'operands' => $numbers
]);
}
/**
* Addition business logic.
*
* @param array $numbers
*/
public function add(array $numbers) {
$this->set([
'result' => $this->operation('+', $numbers),
'operands' => $numbers
]);
}
/**
* Do math operation.
* This is an example, division by 0 and other problematic things not checked.
*
* @param string $operator
* @param array $numbers
* @return int|float
*/
private function operation(string $operator, array $numbers) {
$temp = null;
foreach ($numbers as $n) {
if ($temp === null) {
$temp = $n;
continue;
}
//example, division by 0 and other not checked
switch ($operator) {
case '*':
$temp = $temp * $n;
break;
case '/':
$temp = $temp / $n;
break;
case '-':
$temp = $temp - $n;
break;
case '+':
$temp = $temp + $n;
break;
}
}
return $temp;
}
}
/**
* Our Custom Model
*/
class CalculatorView extends View {
/**
* Class Constructor.
*
* @param CalculatorModel $model
*/
public function __construct(CalculatorModel $model) {
parent::__construct($model);
}
/**
* Build output for multiply.
*/
public function multiply() {
$this->output = $this->generateOutput(
'Multiplication',
$this->data['operands'],
$this->data['result']
);
}
/**
* Build output for divide.
*/
public function divide() {
$this->output = $this->generateOutput(
'Division',
$this->data['operands'],
$this->data['result']
);
}
/**
* Build output for add.
*/
public function add() {
$this->output = $this->generateOutput(
'Addiction',
$this->data['operands'],
$this->data['result']
);
}
/**
* Build output for sub.
*/
public function sub() {
$this->output = $this->generateOutput(
'Subtraction',
$this->data['operands'],
$this->data['result']
);
}
/**
* Generate the output.
*
* @param string $operation
* @param array $operand
* @param type $result
*
* @return string
*/
private function generateOutput(string $operation, array $operands, $result): string {
$string = $operation . ': ';
foreach ($operands as $value) {
$string .= $value . ' * ';
}
$string = substr($string, 0, strlen($string) - 2);
$string .= '= ' . $result;
return $string;
}
}
/**
* Our Custom Controller
*/
class CalculatorController extends Controller {
/**
* Class Constructor.
*
* @param CalculatorModel $model
*/
public function __construct(CalculatorModel $model) {
parent::__construct($model);
}
/**
* Multiply input point.
*
* @param mixed $numbers
*/
public function multiply(... $numbers) {
//check user input
$this->checkOperands($numbers);
$this->filter($numbers);
//manipulate the model
$this->model->multiply($numbers);
}
/**
* Divide input point.
*
* @param mixed $numbers
*/
public function divide(... $numbers) {
//check user input
$this->checkOperands($numbers);
$this->filter($numbers);
//manipulate the model
$this->model->divide($numbers);
}
/**
* Sub input point.
*
* @param mixed $numbers
*/
public function sub(... $numbers) {
//check user input
$this->checkOperands($numbers);
$this->filter($numbers);
//manipulate the model
$this->model->sub($numbers);
}
/**
* Add input point.
*
* @param mixed $numbers
*/
public function add(... $numbers) {
//check user input
$this->checkOperands($numbers);
$this->filter($numbers);
//manipulate the model
$this->model->add($numbers);
}
/**
* Filter user supplied numbers.
*
* @param mixed $numbers
* @throws InvalidArgumentException
*/
private function filter(&$numbers) {
foreach ($numbers as $key => $number) {
switch (gettype($number)) {
case 'string':
$number[$key] = strtonum($number);
break;
case 'integer':
break;
case 'double':
break;
default:
throw new InvalidArgumentException('Not a number');
}
}
}
/**
* Convert a number given as string in the proper type (int or float).
* https://secure.php.net/manual/en/language.types.type-juggling.php
*
* @param string $string Number as string ex '1.0', '0.9' etc
* @return int|float
*/
private function strtonum(string $string) {
if (fmod((float) $string, 1.0) === 0.0) {
return (int) $string;
}
return (float) $string;
}
/**
* Check minimum numbers required for operations.
*
* @param array $numbers
* @throws ArgumentCountError
*/
private function checkOperands(array &$numbers) {
if (count($numbers) < 2) {
throw new ArgumentCountError('At least two numbers needed for operation');
}
}
}
//create components
$model = new CalculatorModel();
$view = new CalculatorView($model);
$controller = new CalculatorController($model);
//attach the observer
$model->attach($view);
//call controller
$controller->multiply(2, 3);
//notify changes to observer
$model->notify();
//call view, build output for selected operation
$view->multiply();
//get the output
echo $view->render();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment