Adapt and get the job done.
Smarty adapter
The latest post concluded with a shortcode class fully working according to the tests.
Sadly fully working meant having nothing printed to the page.
Unsurprisingly looking at the code below
<?php
class idlikethis_Shortcodes_Simple implements idlikethis_Shortcodes_ShortcodeInterface
{
/**
* @var string
*/
protected $template_slug;
/**
* @var array
*/
protected $template_data;
/**
* @var idlikethis_Templates_RenderEngineInterface
*/
protected $render_engine;
/**
* @param idlikethis_Templates_RenderEngineInterface $render_engine
*/
public function __construct(idlikethis_Templates_RenderEngineInterface $render_engine)
{
$this->render_engine = $render_engine;
$this->template_slug = 'shortcodes/simple';
$this->template_data = array(
'text' => __("I'd like this", 'idlikethis'),
);
}
/**
* Returns the shortcode tag.
*
* @return string
*/
public function get_tag()
{
return 'idlikethis';
}
/**
* Returns the shortcode rendered markup code.
*
* @return string
*/
public function render()
{
return $this->render_engine->render($this->template_slug, $this->template_data);
}
/**
* @param $template_slug
*/
public function set_template_slug($template_slug)
{
if (!is_string($template_slug) || empty($template_slug)) {
throw new InvalidArgumentException('Template slug must be a non empty string');
}
$this->template_slug = $template_slug;
}
/**
* @param array $data
*/
public function set_template_data(array $data)
{
$this->template_data = $data;
}
}
The burden of rendering falls on a concrete implementation of the idlikethis_Templates_RenderEngineInterface
interface and the one that’s being provided to the class lacks any logic in the render
method
<?php
class idlikethis_Adapters_SmartyAdapter implements idlikethis_Templates_RenderEngineInterface
{
/**
* Renders a template using the provided data.
*
* @param string $template_slug
* @param array $data
*/
public function render($template_slug, array $data = array()){
// implement me
}
}
So down to a test,
wpcept generate:wpunit wpunit "idlikethis\Adapters\SmartyAdapter"
some test code
<?php
namespace idlikethis\Adapters;
use idlikethis_Adapters_SmartyAdapter as SmartyAdapter;
use Prophecy\Argument;
class SmartyAdapterTest extends \Codeception\TestCase\WPTestCase
{
/**
* @var \Smarty
*/
protected $smarty;
public function setUp()
{
// before
parent::setUp();
// your set up methods here
$this->smarty = $this->prophesize('Smarty');
}
public function tearDown()
{
// your tear down methods here
// then
parent::tearDown();
}
/**
* @test
* it should be instantiatable
*/
public function it_should_be_instantiatable()
{
$sut = $this->make_instance();
$this->assertInstanceOf('idlikethis_Adapters_SmartyAdapter', $sut);
}
/**
* @test
* it should assign no template vars if template data is empty
*/
public function it_should_assign_no_template_vars_if_template_data_is_empty()
{
$sut = $this->make_instance();
$this->smarty->assign(Argument::any())->shouldNotBeCalled();
$this->smarty->display(Argument::any())->willReturn('foo');
$sut->render('some-template', []);
}
/**
* @test
* it should assign template vars
*/
public function it_should_assign_template_vars()
{
$sut = $this->make_instance();
$this->smarty->assign('key1', 'value1')->shouldBeCalled();
$this->smarty->assign('key2', 'value2')->shouldBeCalled();
$this->smarty->display(Argument::any())->willReturn('foo');
$sut->render('some-template', ['key1' => 'value1', 'key2' => 'value2']);
}
/**
* @test
* it should call display method on Smarty
*/
public function it_should_call_display_method_on_smarty()
{
$sut = $this->make_instance();
$this->smarty->assign('key1', 'value1')->shouldBeCalled();
$this->smarty->assign('key2', 'value2')->shouldBeCalled();
$this->smarty->display('some-template.tpl')->willReturn('foo');
$sut->render('some-template', ['key1' => 'value1', 'key2' => 'value2']);
}
private function make_instance()
{
$sut = new SmartyAdapter($this->smarty->reveal());
return $sut;
}
}
and the class code produced by it
<?php
class idlikethis_Adapters_SmartyAdapter implements idlikethis_Templates_RenderEngineInterface
{
/**
* @var Smarty
*/
protected $smarty;
/**
* idlikethis_Adapters_SmartyAdapter constructor.
* @param Smarty $smarty
*/
public function __construct(Smarty $smarty)
{
$this->smarty = $smarty;
}
/**
* Renders a template using the provided data.
*
* @param string $template_slug
* @param array $data
*/
public function render($template_slug, array $data = array())
{
if (!empty($data)) {
array_walk($data, array($this, 'assign_template_var'));
}
$template_slug = ends_with($template_slug, '.tpl') ? $template_slug : $template_slug . '.tpl';
return $this->smarty->display($template_slug);
}
protected function assign_template_var($value, $key)
{
$this->smarty->assign($key, $value);
}
}
Modifying providers, not implementations
The advantage of this approach is that, with a forethought about it, allows for quick changes to be put in place and echo through the dependency tree.
To allow for the idlikethis_Adapters_SmartyAdapter
class to properly instantiate and construct the Shortcodes
service provider changed accordingly.
<?php
class idlikethis_ServiceProviders_Shortcodes extends tad_DI52_ServiceProvider
{
/**
* Binds and sets up implementations.
*/
public function register()
{
$this->container->singleton('idlikethis_Plugin', new idlikethis_Plugin());
$templates_dir = $this->container->resolve('idlikethis_Plugin')->dir_path('templates');
$smarty = new Smarty();
$smarty->setTemplateDir($templates_dir);
$smarty->setCacheDir($templates_dir . '_cache');
$this->container->singleton('Smarty', $smarty);
$this->container->singleton('idlikethis_Templates_RenderEngineInterface', 'idlikethis_Adapters_SmartyAdapter');
$this->container->bind('idlikethis_Shortcodes_ShortcodeInterface', 'idlikethis_Shortcodes_Simple');
$simple_shortcode = $this->container->resolve('idlikethis_Shortcodes_ShortcodeInterface');
add_shortcode($simple_shortcode->get_tag(), array($simple_shortcode, 'render'));
}
/**
* Binds and sets up implementations at boot time.
*/
public function boot()
{
// TODO: Implement boot() method.
}
}
It’s easy to see how, sticking to the single responsibility principle, this service provider is now violating its own: as the name, idlikthis_ServiceProviders_Shortcodes
, suggests its responsibility should be to set up and provide the shortcode classes and tightly related code.
The lines in charge of setting up the main plugin class and the rendering engine are overreaching; I will stick with this solution for the moment and iterate on it.
The plugin class
The idlikethis_Plugin
class is a utility class the responsibility of which is to contextualize the plugin in the current WordPress installation; so pointing to folders and paths and maybe wrapping some more utility methods as I see fit.
Since it has some measure of error prone logic into it it got its own test class as well
wpcept generate:wpunit wpunit "idlikethis\Plugin"
I’m not pasting the code for the sake of brevity.
Maybe some markup?
No, not yet. I’ve repeatedly told this would look like overkill and I know it does but that’s enough code for one article.