Skip to content

Instantly share code, notes, and snippets.

@AmyStephen
Last active December 17, 2015 21:09
Show Gist options
  • Save AmyStephen/5673027 to your computer and use it in GitHub Desktop.
Save AmyStephen/5673027 to your computer and use it in GitHub Desktop.
Example of one way to avoid HTML rendering in your class. From https://twitter.com/AmyStephen/status/339777707695030272
<?php
/**
* Generate a token and email a temporary link to change password and sends to user
*
* @param string $session_id
* @param string $username
*
* @return $this|void
* @since 1.0
*/
public function requestPasswordReset($session_id, $username)
{
$this->verifySession($session_id, 'requestPasswordReset');
$this->verifyUser('requestPasswordReset');
$this->verifyFormToken('requestPasswordReset');
if ($this->error === true) {
$this->redirect_instance->redirect('401', $this->configuration->url_to_login);
}
if ($this->data_instance->getUserData('reset_password_code') == '') {
$this->setResetPasswordCode();
}
$this->updates['last_activity_datetime'] = $this->today;
$this->updateUser();
// 1.
// Normally, a quick and dirty HTML rendering might occur here. It's a simple email with a reset password.
// But, instead, the data needed as input to rendering is placed into an array and passed to the
// mailer_instance that has already been injected into this class. Once rendered, the mailer sends it.
//
$options = array();
$options['type'] = 'password_reset_request';
$options['link'] = $this->configuration->url_to_change_password
. '?reset_password_code=' . $this->data_instance->getUserData('reset_password_code');
$options['name'] = $this->data_instance->getUserData('full_name');
$options['today'] = $this->today;
$options['to'] = $this->data_instance->getUserData('email')
. ', ' . $this->data_instance->getUserData('full_name');
$this->mailer_instance->render($options)->send();
return 0;
}
?>
<?php
/**
* Set the Option Values, Initiate Rendering, Send
*
* @param array $options
* @param null|TemplateInterface $template_instance
*
* @return $this
* @since 1.0
* @throws \Molajo\User\Exception\MailerException
*/
public function render(
$options = array(),
TemplateInterface $template_instance = null
) {
if (is_array($options)) {
} else {
$options = array();
}
if (count($options) > 0) {
$this->data = new \stdClass();
foreach ($this->property_array as $property) {
if (isset($options[$property])) {
$this->$property = $options[$property];
}
}
foreach ($options as $key => $value) {
$this->data->$key = $value;
}
}
// 2.
// No, the mailer does not render either. The mailer passes the job off to the render method in
// the template instance. The mailer's job is to mail stuff, not to render. No HTML in here, either.
//
if ($template_instance === null) {
} else {
$this->template_instance = $template_instance;
}
$results = $this->template_instance->render($this->data);
$this->subject = $results->subject;
$this->body = $results->body;
return $this;
}
<?php
/**
* Text Template Class
*
* @package Molajo
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2013 Amy Stephen. All rights reserved.
*/
namespace Molajo\User\Utilities;
use stdClass;
use Molajo\User\Api\FieldHandlerInterface;
use Molajo\User\Api\MessagesInterface;
use Molajo\User\Api\TemplateInterface;
/**
* Text Template Class
*
* @author Amy Stephen
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2013 Amy Stephen. All rights reserved.
* @since 1.0
*/
class TextTemplate implements TemplateInterface
{
/**
* FieldHandler Instance
*
* @var object Molajo\User\Api\FieldHandlerInterface
* @since 1.0
*/
protected $fieldhandler_instance;
/**
* Messages Instance
*
* @var object Molajo\User\Api\MessagesInterface
* @since 1.0
*/
protected $messages_interface;
/**
* Default Exception
*
* @var string
* @since 1.0
*/
protected $default_exception = 'Molajo\\User\\Exception\\TemplateException';
/**
* Input
*
* @var array
* @since 1.0
*/
protected $data = array();
/**
* Rendered Output
*
* @var object
* @since 1.0
*/
protected $rendered_output;
/**
* Tokens
*
* @var array
* @since 1.0
*/
protected $tokens;
/**
* Templates
*
* @var object
* @since 1.0
*/
protected $templates = array();
/**
* List of Properties
*
* @var object
* @since 1.0
*/
protected $property_array = array(
'fieldhandler_instance',
'messages_instance',
'data',
'rendered_output',
'tokens',
'templates'
);
/**
* Construct
*
* @param FieldHandlerInterface $fieldhandler_instance
* @param MessagesInterface $messages_instance
* @param string $default_exception
* @param array $templates
* @param array $data
*
* @since 1.0
*/
public function __construct(
FieldHandlerInterface $fieldhandler_instance,
MessagesInterface $messages_instance,
$default_exception = null,
array $templates = array(),
array $data = array()
) {
$this->fieldhandler_instance = $fieldhandler_instance;
$this->messages_instance = $messages_instance;
$this->templates = $templates;
$this->data = $data;
if ($default_exception === null) {
} else {
$this->default_exception = $default_exception;
}
}
/**
* Get the current value (or default) of the specified key
*
* @param string $key
* @param mixed $default
*
* @return mixed
* @since 1.0
* @throws \Molajo\User\Exception\TemplateException
*/
public function get($key, $default = null)
{
$key = strtolower($key);
if (in_array($key, $this->property_array)) {
} else {
$this->messages_instance->throwException(6000, array('key' => $key), $this->default_exception);
}
if ($this->$key === null) {
$this->$key = $default;
}
return $this->$key;
}
/**
* Set the value of a specified key
*
* @param string $key
* @param mixed $value
*
* @return $this
* @since 1.0
* @throws \Molajo\User\Exception\TemplateException
*/
public function set($key, $value = null)
{
$key = strtolower($key);
if (in_array($key, $this->property_array)) {
} else {
$this->messages_instance->throwException(6010, array('key' => $key), $this->default_exception);
}
$this->$key = $value;
return $this;
}
/**
* Set the Option Values, Initiate Rendering, Send
*
* @param stdClass $data
*
* @return string
* @since 1.0
*/
public function render(stdClass $data)
{
// 3.
// It says "render", but there is no HTML in here. The function of this class is to look for tokens
// and replace those values with data. It loops over the HTML until no more tokens are found.
// Once it has determined such is the case, the rendered output is returned to the mailer for sending.
//
// So, where is the HTML? It's in the $this->templates array which has been injected into the
// Template class (this class) during construction by the Inversion of Control Container .
//
$this->data = $data;
$type = $data->type;
$this->rendered_output = '';
$rendered = new stdClass();
/** Body */
if (isset($this->templates[$type])) {
$this->rendered_output = $this->templates[$type]->body;
} else {
if (isset($this->data['body'])) {
$this->rendered_output = $data['body'];
}
}
$this->renderLoop();
$rendered->body = $this->fieldhandler_instance
->filter($type . ' Body', $this->rendered_output, 'Fullspecialchars');
/** Head */
if (isset($this->templates[$type])) {
$this->rendered_output = $this->templates[$type]->subject;
} else {
if (isset($this->data['subject'])) {
$this->rendered_output = $data['subject'];
}
}
$this->renderLoop();
$rendered->subject = $this->fieldhandler_instance
->filter($type . ' Body', $this->rendered_output, 'Fullspecialchars');
return $rendered;
}
/**
* Iteratively process content, replacing tokens with data values
*
* @return $this
* @since 1.0
*/
protected function renderLoop()
{
$complete = false;
$loop = 0;
while ($complete === false) {
$loop ++;
$this->parseTokens();
if (count($this->tokens) == 0) {
break;
}
$this->replaceTokens();
if ($loop > 100) {
break;
}
continue;
}
return $this;
}
/**
* Parses the rendered output, looking for {tokens}
*
* @return $this
* @since 1.0
*/
protected function parseTokens()
{
$this->tokens = array();
preg_match_all('#{(.*)}#iU', $this->rendered_output, $this->tokens);
return $this;
}
/**
* Locate all tags and replace with data
*
* @return $this
* @since 1.0
*/
protected function replaceTokens()
{
$get_rid_of_array = array();
foreach ($this->tokens[0] as $get_rid_of_item) {
$get_rid_of_array[] = $get_rid_of_item;
}
$replace_with_array = array();
foreach ($this->tokens[1] as $use_as_field_name) {
if (isset($this->data->$use_as_field_name)) {
$replace_with_array[] = $this->data->$use_as_field_name;
} else {
$replace_with_array[] = '';
}
}
$this->rendered_output = str_replace($get_rid_of_array, $replace_with_array, $this->rendered_output);
return $this;
}
}
<?php
/**
* User Template Dependency Injector
*
* @package Molajo
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2013 Amy Stephen. All rights reserved.
*/
namespace Molajo\Services\UserTemplate;
use stdClass; // this is needed by required files in getTemplates() method below
use Exception;
use Molajo\IoC\Handler\CustomInjector;
use Molajo\IoC\Api\InjectorInterface;
use Molajo\IoC\Exception\InjectorException;
/**
* User Template Dependency Injector
*
* @author Amy Stephen
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @copyright 2013 Amy Stephen. All rights reserved.
* @since 1.0
*/
class UserTemplateInjector extends CustomInjector implements InjectorInterface
{
/**
* Constructor
*
* @param array $options
*
* @since 1.0
*/
public function __construct($options = array())
{
$this->service_namespace = 'Molajo\\User\\Utilities\\TextTemplate';
parent::__construct($options);
}
/**
* Instantiate Class
*
* @param bool $create_static
*
* @return object
* @since 1.0
* @throws InjectorException
*/
public function instantiate($create_static = false)
{
$getService = $this->getService;
$fieldhandler_instance = $getService('UserFieldHandler');
$messages_instance = $getService('UserMessages');
// 4.
// The IoC Container injects the template into the TextTemplate Class above during construction
// Below, it retrieves the file based on runtime parameters - or retrieves alll
//
$templates = $this->getTemplates();
$default_exception = 'Molajo\\User\\Exception\\TemplateException';
try {
$class = $this->service_namespace;
$this->service_instance = new $class(
$fieldhandler_instance,
$messages_instance,
$default_exception,
$templates
);
} catch (Exception $e) {
throw new InjectorException
('IoC: Injector Instance Failed for Molajo\User\Utilities\TextTemplate'
. $e->getMessage());
}
return $this;
}
/**
* Retrieve requested template in the $options array or load all templates
*
* @return array
* @since 1.0
* @throws \Molajo\IoC\Exception\InjectorException
*/
public function getTemplates()
{
if (isset($this->options['language'])) {
$language = $this->options['language'];
} else {
$language = 'en-GB';
}
$template_folder = __DIR__ . '/' . $language . '/';
if ((is_dir($template_folder))) {
} else {
if ($language === 'en-GB') {
} else {
$template_folder = __DIR__ . '/' . 'en-GB' . '/';
}
if ((is_dir($template_folder))) {
} else {
throw new InjectorException
('IoC: Injector Instance Failed for Molajo\User\Utilities\TextTemplate '
. 'Template folder does not exist ' . $template_folder);
}
}
$template_files = array();
if (isset($this->options['template_name'])) {
$template_files[] = $this->options['template_name'];
} else {
$temp = scandir($template_folder);
foreach ($temp as $file) {
if ($file === '.' || $file == '..') {
} else {
$template_files[] = $file;
}
}
}
if (count($template_files) > 0) {
} else {
throw new InjectorException
('IoC: Injector Instance Failed for Molajo\User\Utilities\TextTemplate '
. 'Template folder does not contain templates: ' . $template_folder);
}
$templates = array();
foreach ($template_files as $template_name) {
if (file_exists($template_folder . $template_name)) {
require $template_folder . $template_name;
$templates[] = $template; // $template is defined in the included file
} else {
throw new InjectorException
('IoC: Injector Instance Failed for Molajo\User\Utilities\TextTemplate '
. ' Template File does not exist. (This should never happen.');
}
}
return $templates;
}
}
<?php
// 5.
// Not a great example since it's a simple Text Template for an email "password reset" request, but, it is easy to see
// how separating out the source template from the PHP classes, a great deal of flexibility results. Given this
// separation and the use of Interfaces, swapping out the TextTemplate class for a more robust solution, like
// Mustache or Twig, is simple. No core hacks, just adapt the DI load, make certain the Interfaces fit and go.
// More importantly, the template is in the hands of those who know what to do with it, that being frontend
// devs. A lot of times, the question is asked why frontend devs don't participate enough in open source
// projects and a lot of the reason is, we've hidden their HTML in PHP classes and they haven't a clue where
// to get started. Change that - and vow to never generate HTML in your PHP classes anymore and my guess is
// those important resources to open source projects will start getting involved.
//
$template = new stdClass();
$template->name = 'password_reset_request';
$template->subject = 'Password Reset Request';
$template->body = 'On {today}, {name} requested a password reset. The link expires when the password is '
. 'changed or the next time the account is logged in. Please click {link} to change the password now.';
@AmyStephen
Copy link
Author

Look for the five comment boxes above to see how the rendering process is abstracted out of the PHP classes so that it can be managed better by frontend devs.

My personal goal is to never generate any HTML inside of a PHP class again.

That is not to say I will not use PHP as a Templating engine. I will - I don't mind it in that way, at all. I will also make certain Twig and Mustache can be substituted in but PHP is fine as a Templating engine. The trick is to keep the logic out of the Template - "logic free" no matter what you use.

I am just going to be careful to never trap HTML away from front-end devs by burying it inside of PHP classes. Separately the html into "their files" is the best way to ensure our "better half" in our open source communities, those who have some idea about user interfaces, can take these powerful engines and put 'em to good use. =)

(Also, easier to translate, to create stunning HTML email templates, to swap out for Mail Chimp, etc., etc., etc.) All good stuff.

Links

Thanks to so many of you for your help as I learn object oriented approaches and how to code PHP properly. Please feel free to give me any kind of feedback, especially if you see problems with my approach. I might object, I might argue my point of view, but I will listen. Looking back over this past year, it was those of you who dared to tell me I was wrong who helped me learn. Appreciate it. Very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment