After experimenting with different design approaches for the past two years, I've created a library for managing and processing dynamic content to be displayed on a single HTML view. This library is PSR-0, PSR-1 and PSR-2 compliant. I'm quite happy with this design but want to make sure it can stand excessive use.
It's simple enough until you start dealing with nested data. To give a quick summary, each single piece of content on a page can be dynamically changed depending on its associated GET parameter. This is what I've called a Slot.
For exmaple, on a page, I might have a Slot for a headline, which would be bound to Get parameter h
, so ?h=3
would show a different headline to ?h=2
. I've called each item of content in a slot a Card. The card value of a slot it retrieved through the following method.
<?php
$page = new Page($data);
$headline = $page->get('headline');
?>
<h1><?=$headline?></h1>
However, a slot may contain another slot, where for example, a headline could have the content
"Welcome back, {username} from {location}"
and each tag name is processed and existing Slot objects with those names are injected into its parent object.
- Should I instead try and create a reference to those slots instead of injecting them?
- Would this consume a lot of memory if someone tried to nest them multiple times, or if there are many nested slots or if a slot holds 10 000 possible cards?
- Below is the main class
Page
, where the slots are injected in the constructor. Followed by theSlot
class, should this be done at instantiation and/or should I have a method for updating the configuration?
If you need more scope, the repository is currently on GitHub
Page.php
<?php
namespace Kamereon;
use Symfony\Component\HttpFoundation\Request;
/**
* The base for a new dynamic landing page. Each dynamic placeholder is called a slot
* where a slot will hold many cards for one to be displayed depending on a set of
* given parameters.
*
* @author Adam
*/
class Page
{
/**
* The Symfony HttpFoundation Request object.
*/
protected $request;
/**
* Raw configuration data.
*/
protected $config = array();
/**
* Collection of Slot objects.
*/
protected $slots = array();
/**
* Loads the config data and creates new Slot instances.
*
* @param array $config
*/
public function __construct(array $config)
{
$this->request = Request::createFromGlobals();
$this->config = $config;
// created new instances for each slot
foreach ($config['slots'] as $slotName => $slotData) {
$this->slots[$slotName] = new Slot($slotName, $slotData);
}
// inject nested slots
foreach ($config['slots'] as $slotName => $slotData) {
if (isset($slotData['nestedWith']) && count($slotData['nestedWith']) > 0) {
foreach ($slotData['nestedWith'] as $nestedSlotName) {
$this->slots[$slotName]->addNestedSlot($this->slots[$nestedSlotName]);
}
}
}
}
/**
* Get the configuration array.
*
* @return array
*/
public function getConfig()
{
return $this->config;
}
/**
* Get a Slot object from the slot collection by its key.
*
* @param string $slot
* @return Slot
*/
public function getSlot($slot)
{
return $this->slots[$slot];
}
/**
* Get the card value for a slot.
*
* @param string $slotName
* @param string $default
* @return string
*/
public function get($slotName, $default = '0')
{
$slot = $this->slots[$slotName];
try {
$card = $slot->getCard($this->request->get($slot->getKeyBind(), $default));
} catch (\Exception $e){
$card = '';
}
if ($slot->getHasNestedSlots()) {
foreach ($slot->getNestedSlots() as $nestedSlot) {
try {
$nestedCards[$nestedSlot->getName()] = $nestedSlot->getCard(
$this->request->get($nestedSlot->getKeyBind(), $default)
);
} catch (\Exception $e){
$nestedCards[$nestedSlot->getName()] = '';
}
}
foreach ($nestedCards as $cardName => $cardValue) {
$card = str_replace(
sprintf('{%s}', $cardName),
$cardValue,
$card
);
}
}
return $card;
}
/**
* Override the request instance by injecting your own.
*
* @param Request $request
*/
public function setRequest(Request $request)
{
$this->request = $request;
}
/**
* Get the request instance.
*
* @return Request
*/
public function getRequest()
{
return $this->request;
}
}
Slot.php
<?php
namespace Kamereon;
/**
* A placeholder for variable content on a page, which a value will be assigned
* to it as a Card instance
*
* @author Adam
*/
class Slot
{
/**
* The name of the slot
*/
protected $name;
/**
* The key name that is bound to the slot
* A key can be shared with another slot
*/
protected $keyBind;
/**
* An array of the names of nested slots
*/
protected $nestedSlotNames = array();
/**
* The collection array of nested Slot objects
*/
protected $nestedSlots = array();
/**
* A list of cards for each one will be displayed on the page
*/
protected $cards = array();
/**
* Create new slot with name, key binding and its cards
* and if the slot has nested slots, assign only the names of
* those slots.
*
* @param string $name
* @param array $data
*/
public function __construct($name, array $data)
{
$this->name = $name;
$this->keyBind = $data['keyBind'];
$this->cards = $data['cards'];
if (isset($data['nestedWith'])) {
$this->nestedSlotNames = $data['nestedWith'];
}
}
/**
* Get the name of the slot
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Add a slot to the nested slots collection
*
* @param Slot $slot
*/
public function addNestedSlot(Slot $slot)
{
$this->nestedSlots[$slot->getName()] = $slot;
}
/**
* Get all nested slots
*
* @return array
*/
public function getNestedSlots()
{
return $this->nestedSlots;
}
/**
* Get specific nested slot
*
* @return Slot
*/
public function getNestedSlotByName($name)
{
return $this->nestedSlots[$name];
}
/**
* Get a value of a card by its index / array key.
* Throws an InvalidArgumentException if the key does not exist.
*
* @return string
*/
public function getCard($index)
{
if (array_key_exists($index, $this->cards)) {
return $this->cards[$index];
}
throw new \InvalidArgumentException(sprintf(
'Card with index "%s" for slot "%s" does not exist', $index, $this->name));
}
/**
* Get the binded key
*
* @return string
*/
public function getKeyBind()
{
return $this->keyBind;
}
/**
* Check if a slot contains other slots nested within
*
* @return boolean
*/
public function getHasNestedSlots()
{
return count($this->nestedSlots) > 0;
}
}