Name class #130

Open
funivan opened this Issue Nov 30, 2016 · 12 comments

Projects

None yet

2 participants

@funivan
funivan commented Nov 30, 2016

Is it possible to provide custom name strategy class?

@ragboyjr
Contributor

@funivan Can you give an example?

@funivan
funivan commented Dec 24, 2016

If user open site m.test.com I want to render mobile views if they exist
Engine check if mobile template exists profile.mobile.html and render it. Or use profile.html

// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');
echo $templates->render('profile.html'); 
@ragboyjr
Contributor

@funivan Yup totally understood. That sounds like a handy feature. For this specific strategy, you'd need to inject a request object into it, so we'll have to consider a good way to structure this concept.

@funivan
funivan commented Dec 25, 2016

@ragboyjr I do not need to inject request. For example some prototype:

class OnRequestEvent {
  public function registerPlates(Event $event){
    $isMobile = $event->getRequest()->has('isMobile')
    $naming = new MyCustomTemplatesnaming();
    $naming->setCheckMobileTemplates($isMobile);
    $templating = new League\Plates\Engine('/path/to/templates', $naming);
    Container::instance()->register('templating', $templating);
  }
}

I can configure template engine on request and then add it to the container.
The main problem: I cant configureNameclass. It will be cool to haveNameInterface` ot something like that.

@ragboyjr
Contributor
ragboyjr commented Dec 25, 2016 edited

@funivan

I didn't mean inject the request into the engine, but you'd need to inject the request or at least the user-agent into the specific naming strategy. Similar to what you do here. The issue with your example is that you likely would setup your service definitions BEFORE any request handling has started. So, I think your example would more like so:

class OnRequestEvent {
  public function registerPlates(Event $event){
    $isMobile = $event->getRequest()->has('isMobile')
    $naming = new MyCustomTemplatesnaming();
    $naming->setCheckMobileTemplates($isMobile);
    Container::instance()->get('templating')->addNaming($naming);
  }
}
@funivan
funivan commented Dec 25, 2016

@ragboyjr yep. You are correct =)
IMHO setNaming would be better ;)

@ragboyjr
Contributor
ragboyjr commented Dec 25, 2016 edited

@funivan

Sure, i'm not tied to specific func name at this point.

However, we might want a stack of naming strategies kind of like middleware because we might want to apply several naming strategies. Here are just a few naming strategies that would be nice to exist together

  • Mobile naming strategy
  • Site Language naming strategy - think of an en vs fr sub folder structure that is determined via session/request
  • Relative Partial naming strategy - partials would be loaded relative to the current template or fallback to the normal naming strategy.
  • Underscored partials strategy - only load partial files that start with an _ similar to Sass or RoR.

In a stacked/middleware system, you could easily have each of those strategies to do their part or delegate to the next naming strategy.

@funivan
funivan commented Dec 25, 2016

At first it will be cool to have interface for the name class. And then anyone can create its own name strategy.

@ragboyjr
Contributor
ragboyjr commented Dec 28, 2016 edited

@funivan @reinink here is a list of naming strategies that I think should be supported for 4.0.

  • Custom - Any user defined strategy to implement something like what @funivan needs

  • Relative Paths - Any path starting with . will be loaded relative to the current templates path.
    ./partial.php or ../partial.php are all valid

  • Automatic Partials

    views/blog/main.phtml
    views/blog/_title.phtml
    ``
    if in `main.phtml` you do `$this->fetch('title')` it would see if the `_title.phtml` file exists in the current directory first, else it would load like a normal template.
    
  • Themes/Fallback - It would check views/theme/winter/blog/main.phtml then views/blog/main.phtml if the file didn't exist. This would allow for theme extension.

It'd be nice if each of these was configurable so you can remove or add those if wanted. So maybe like a stack of middleware or just use plain decoration?

So something like

new ChainLoader([new ThemeLoader(), new RelativePathLoader()])

vs

mw\compose([new ThemeLoader(), new RelativePathLoader()])

Any thoughts on this list or ideas? I'm open for improvement.

@funivan
funivan commented Dec 29, 2016

@ragboyjr in my case it will be wrong to use CustomName Strategy and Automatic Partials. Automatic Partials does not change _title.mobile.phtml

  • #59 (load templates from different folders) is not compatible with ThemeLoader

With yours approach developers can combine only compatible strategies in the ChainLoader. In some cases it can be confusing IMHO

When developers want to manage themes and relative path then they can implement interface in their own library.
If there will be more requests on the Relative Path name strategy then we can add option to the default NameStrategy class

$naming = new NameStrategy();
$naming->setuseRelativepath(true);
@ragboyjr
Contributor
ragboyjr commented Dec 29, 2016 edited

@funivan thanks for the feedback.

Two things

First, I was more referring to what features I think should be supported for 4.0. If we don't decide to make configurable via stack/middleware, then that's fine, but I'm just listing a set of features that we can think about in regards to naming strategy.

Second, regarding configurability, user's would have the ability to create one naming strategy that can do everything they need, or they can decide to reuse other naming strategies in stacks. It'd be their choice. Here's a little example I coded up which I think could serve as a Proof of Concept.

<?php

/** resolves a name to an actual path */
interface ResolveName {
    public function __invoke($name, array $ctx);
}

/** automatically appends suffix */
function suffixResolveName($resolve, $suffix = '.php') {
    return function($name, array $ctx) use ($resolve, $suffix) {
        return $resolve($name . $suffix, $ctx);
    };
}

/** resolve paths from a set of directories */
function directoryResolveName($dirs) {
    return function($name, array $ctx) use ($dirs) {
        list($exists) = $ctx;

        foreach ($dirs as $dir) {
            $path = $dir . '/' . $name;
            if ($exists($path)) {
                return $path; 
            }
        }
    };
}

/** will allow auto partials */
function autoPartialResolveName($resolve) {
    return function($name, array $ctx) use ($resolve, $traverse) {
        list($exists, $cur_path) = $ctx;

        $cur_dir = dirname($cur_path);
        $path = $cur_dir . '/_' . $name;
        if ($exists($path)) {
            return $path;
        }

        return $resolve($name, $ctx);
    };
}

/** will resolve relative paths */
function relativePathResolveName($resolve) {
    return function($name, array $ctx) use ($resolve) {
        list($exists, $cur_path) = $ctx;
        if ($name[0] != '.') {
            return $resolve($name, $ctx);
        }

        $path = dirname($cur_path) . '/' . $name;
        if ($exists($path)) {
            return $path;
        }

        return $resolve($name, $ctx);
    };
}

/** accepts a resolver and a psr-7 request */
function skinTypeResolveName($resolve, $request) {
    return function($name, array $ctx) use ($resolve, $request) {
        $is_mobile = $request->getAttribute('is_mobile');
        if ($is_mobile) {
            $name .= '.mobile';
        }

        return $resolve($name, $ctx);
    };
}

/** This would be the default resolver. So all the complexity of the decorators would be hidden for most
    users. (in fact, they wouldn't every see this unless they wanted to build their own renderer). Any advanced 
    users could create their own stack of decorators. */
function platesResolveName(array $dirs, $ext = '.php') {
    $resolve = directoryResolveName($dirs);
    $resolve = autoPartialResolveName($resolve);
    $resolve = relativePathResolveName($resolve);
    $resolve = suffixResolveName($resolve, $ext);
    return $resolve;
}

// create the resolver
$resolve = platesResolveName([__DIR__ . '/theme/optional', __DIR__ . '/theme/standard'], '.phtml');
$resolve = skinTypeResolveName($resolve, $request);

// pass in the template name to render and then add in the context which is a two-tuple of file exists func and current template path
$resolve('tpl-name', ['file_exists', __DIR__ . '/theme/standard/folder/template.phtml']);

As you can see, you can use the decorators if you want, or you can simply just implement one resolver function to do anything that you wanted. But by creating the standard ones composable , you give users the ability to re-use logic.

Also, in my example, the auto partial and mobile specific template feature would work together because you wouldn't want to use the mobile template then default to the desktop template. And if you wanted more custom fallback procedures, you can create your own custom one do to whatever you want.

@funivan
funivan commented Dec 29, 2016

But by creating the standard ones compassable, you give users the ability to re-use logic.

Yep. I agree with you. In that case developers can chose what to do: re-use or implement.
Usage of the stack can be a good option.

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