Search code examples
phpabstract-classcode-duplication

Remove duplicate code from abstract children


I'm creating templating engine for a software we are building. I have an abstract class,Template, with 3 extended classes Header, Body, Footer. Each of the 3 children contain the same constructor code which is bothering me just looking at as far as duplication in code, the constuctors all have this:

public function __construct($file, array $kvp)
    {
        $this->templates[] = array(
            'context' => $this->getClass($this),
            'file' => $file,
            'kvp' => $kvp
        );
    }

Is there a way to move this code into the parent class or cut down on duplication? Below is the full code:

<?php

Interface iTemplate
{   
    protected function add(Template $template);
    protected function remove(Template $template);
}

abstract class Template implements iTemplate
{
    protected $html = array();
    protected $templates = array();

    public function add($template)
    {
        array_push($this->templates, $template);
    }

    public function remove($template)
    {
        if(in_array($template, $this->templates))  {
            $key = array_search($template, $this->templates);
            $this->templates[$key] = null;
            $this->templates = array_filter($this->templates);
        }
    }

    protected function getClass($context)
    {
        return get_class($context);
    }

    public function render()
    {
        foreach($this->templates as $template)
        {
            $output = file_get_contents($template->file);

            foreach ($this->template->kvp as $key => $value) {
                $tagToReplace = "[@$key]";
                $output = str_replace($tagToReplace, $value, $output);
            }

            $html[] = $output;
        }

        return implode("\n", $html);
    }
}

class Header extends Template
{   
    public function __construct($file, array $kvp)
    {
        $this->templates[] = array(
            'context' => $this->getClass($this),
            'file' => $file,
            'kvp' => $kvp
        );
    }
}

class Body extends Template
{   
    public function __construct($file, array $kvp)
    {
        $this->templates[] = array(
            'context' => $this->getClass($this),
            'file' => $file,
            'kvp' => $kvp
        );
    }
}

class Footer extends Template
{   
    public function __construct($file, array $kvp)
    {
        $this->templates[] = array(
            'context' => $this->getClass($this),
            'file' => $file,
            'kvp' => $kvp
        );
    }
}


/* Example */

$Template = new Template();
$Template->add(new Header($header_file, $header_kvp));
$Template->add(new Body($body_file, $body_kvp));
$Template->add(new Footer($footer_file, $footer_kvp));
echo $Template->render();

Solution

  • Simple solution is using trait:

    
    interface iTemplate
    {   
        public function add(Template $template);
        public function remove(Template $template);
    }
    
    class Template implements iTemplate
    {
        protected $html = array();
        protected $templates = array();
    
        public function add(Template $template)
        {
            //...
        }
    
        public function remove(Template $template)
        {
            //...
        }
    
        protected function getClass()
        {
            return get_class($this);
        }
    
        public function render()
        {
            //...
        }
    }
    
    class Header extends Template
    {   use ConstructTrait;
    }
    
    class Body extends Template
    {   use ConstructTrait;
    }
    
    class Footer extends Template
    {   use ConstructTrait;
    }
    
    trait ConstructTrait
    {
      public function __construct($file, $kvp)
      {
        $this->templates[] = array(
          'context' => $this->getClass(),
          'file' => $file,
          'kvp' => $kvp
          );
      }
    }