Basic PHP template loader
Technical mistakes

echo void

echo $tpl-&gt;render($_GET['tpl']);


This echo will display empty string (null), because you've already output a string with echo within method and have void return type (implicit return null). Make render() method return a string, because you want to defer output as much as you can. You won't be able to do anything with it after that (including headers management). Eventually you might want to use psr-7 response object with stream and plug yourself into some framework - class with method that returns string can be left untouched (Open-closed Principle) and wrapped with an object that changes string into stream.

closing tag

?&gt;


Simply don't use it at the end of file. It causes hard to debug problems with undesired output - more here.

Algorithm

Render method works on hardcoded strings, which rarely is a good idea. You can make it more generic, as the logical pattern is quite distinct. Append path &amp; file extension to given name and get file contents if it exists. The only thing you need to be careful about is path traversal attack - if you allow only lowercase letters it won't be possible. Also some error handling will be required.

OOP

Api

First of all your class api is perfect. Giving only template name, without implying that file system is involved makes it encapsulated abstraction and that's what OOP is about - don't be fooled by magic tricks and utilities. It is really a hard part, that often goes into area of so called "unknown unknowns" if you get it wrong, and it is really easy to do so.

Naming

You can write a book about that, so I'll point a few things:


Avoid naming your classes with names ending with -or or -er. It fits interfaces pretty well - they're focused on delivering something specific (from client's perspective) in unspecified way.
On the other hand avoid mentioning IO gateway names in data focused interfaces. It fits class names - they're interface implementations delivering something by talking to concrete IO mechanism.
Your TemplateLoader is concrete kind of loader, but you should be able to switch it with implementation that delivers strings from database or memory (when testing the class that uses it). You won't see the point when global namespace is the client using it, but when additional layers wrap it up it won't require messing with code beside the place where object composition happens.
render() method usually implies that some data processing is involved - you are simply returning template contents.


Refactored

Since this class is small (as it should) I've written refactored version based on the things I've pointed out and type hints for php 7:

interface TemplateLoader
{
    /**
     * @param string $templateName
     *
     * @throws InvalidArgumentException|TemplateNotFoundException
     *
     * @return string
     */
    public function contents(string $templateName): string;
}

class TemplateFiles implements TemplateLoader
{
    private $templatesDirectory;

    public function __construct(string $templatesDirectory = '') {
        $this-&gt;templatesDirectory = $templatesDirectory;
    }

    public function contents(string $templateName): string
    {
        $filename = $this-&gt;templateFilename($templateName);
        return file_get_contents($this-&gt;existingFile($filename));
    }

    private function templateFilename(string $templateName): string
    {
        $file = $this-&gt;validName($templateName) . '.php';
        return rtrim($this-&gt;templatesDirectory, '/') . '/' . $file;
    }

    private function validName(string $template): string
    {
        if (preg_match('/[^a-z]/', $template)) {
            throw new InvalidArgumentException('...');
        }

        return $template;
    }

    private function existingFile(string $filename): string
    {
        if (!file_exists($filename)) {
            throw new TemplateNotFoundException('...');
        }

        return $filename;
    }
}


You would have to implement two exceptions and put method call into try-catch block. There are two of them, because one may come from potentially malicious request (you might want to log these), while the other may be dev error or bypassed UI by user (it's not control flow exception which should be avoided).