Giter VIP home page Giter VIP logo

Comments (9)

ncaneldiee avatar ncaneldiee commented on May 19, 2024

How about change private $baseroute = ''; to public $baseroute = ''; So,

$router = new \Bramus\Router\Router();

// before route middleware
$router->before('GET', '/(.*)', function($slug) {
    if (in_array(segment(1), ['en', 'id'])) {
        Session::set('language', segment(1));
    } else {
        redirect(DOMAIN . $slug);
    }
});

$router->baseroute = '/' . segment(1);

$router->all('/(.*)', function($slug) {
    echo $slug; // domain.com/en/title -> title
});

I just thinking about on/off multilingual, like

$ismultilingual = true;

$router = new \Bramus\Router\Router();

if ($ismultilingual) {
    // before route middleware
    $router->before('GET', '/(.*)', function($slug) {
        if (in_array(segment(1), ['en', 'id'])) {
            Session::set('language', segment(1));
        } else {
            redirect(DOMAIN . $slug);
        }
    });

    $router->baseroute = '/' . segment(1);
}

$router->all('/(.*)', function($slug) {
    echo $slug; // domain.com/en/title -> title
});

from router.

tleb avatar tleb commented on May 19, 2024

If I had to manage a multilingual site, I would do something like this (of course, you should have your class in a different file):

class Router extends \Bramus\Router\Router
{
    /**
     * List of accepted languages
     * @var array
     */
    public $langs = [
        'en',
        'id',
    ];

    /**
     * Default language
     * @var string
     */
    public $default_lang = 'en';

    protected function getCurrentUri()
    {
        $uri = parent::getCurrentUri();
        $uri = $this->parseLang($uri);

        return $uri;
    }

    /**
     * Parse the uri string
     *
     * @param string $uri
     * @return string
     * @throws \Exception
     */
    protected function parseLang($uri)
    {
        $arr_uri = explode('/', $uri);
        if (in_array($arr_uri[1], $this->langs)) {
            // if a lang was given and it is in the list
            $lang = $arr_uri[1];
            $uri  = $arr_uri[2];
        } else {
            // if no lang was given or the one given is not in the list

            if (!in_array($this->default_lang, $this->langs)) {
                // exception if the default language is not in the list
                throw new \Exception('The default language is not valid.');
            }

            $lang = $this->default_lang;
        }

        $this->useLang($lang);

        return $uri;
    }

    /**
     * Use the language string
     * @param string $lang Language string
     */
    protected function useLang($lang)
    {
        // do whatever you want with lang
        var_dump($lang);
    }
}

$router = new Router;

$router->get('/hello', function () {
    echo 'Hello World!';
});

$router->run();

What this does is quite simple:

  • if a language is given, it removes it from the uri;
  • if no language is given or the language is invalid, it uses the default language;
  • if the default language is not in the lang array, it throws an exception;
  • it calls the Router::useLang($lang) method and passes it the language (even if the default was used).

I will do a PR to change the visibility of Router::handle() from private to protected, so that a the signature of Router::run() can be modified. If this is accepted, you would be able to do this:

$router->run(null, function ($lang) { /* use $lang */ });

This would lead to a cleaner code than having a Router::useLang() method.

Another option would be to have a property containing a callable, which would be called (if it is callable) by Router::parseLang().

from router.

bramus avatar bramus commented on May 19, 2024

Hi all,

Checking the replies further I can see benefits into making $baseroute accessible from outside the class itself, as suggested by @ncaneldiee. It does indeed allow one to get the wanted result rather quickly. It has some downsides however:

  • it necessitates the availability of the posted segment function
  • the codefragment which alters $baseroute is quite mindboggling
  • the handling function does not know which language it is serving (using the session to persist it is a hack on a hack ;))

The suggested quick fix to "just use mount()" also has the first downside mentioned above: it necessitates the availability of the segment function. On the upside though said function could be altered in such a way so that it doesn't need to persist the $language in the session.

Changing the visibility of Router::handle() as suggested by @tleb is not totally clear to me right now. I think I'm missing the actual change to Router::run() in order to see how it would work. I'm also a bit sceptical about it right now, as it would require two adjustments: one to Router::handle() and one to Router::run().

Based on the idea of the custom Router class as suggested by @tleb I had my own take at this problem. The result is a MultilangRouter class which:

  • Is straightforward (it reads rather easy if I say so myself)
  • Is self-organising: if the requested language is not one of the specified ones it redirects to the default language
  • Requires no change in the base Bramus\Router\Router
  • Passes the language to the callback function so that it knows which language it is serving
<?php

// Require the base Router classes
require_once __DIR__ . '/../src/Bramus/Router/Router.php';

/**
 * A Multilingual Router
 */
class MultilangRouter extends \Bramus\Router\Router {

    /**
     * The Default language
     * @var string
     */
    private $defaultLanguage;

    /**
     * List of allowed languages
     * @var array
     */
    private $allowedLanguages= [];

    /**
     * A Multilingual Router
     * @param array  $allowedLanguages
     * @param string $defaultLanguage
     */
    public function __construct(array $allowedLanguages, $defaultLanguage) {

        // Store passed in data
        $this->allowedLanguages = $allowedLanguages;
        $this->defaultLanguage = (in_array($defaultLanguage, $allowedLanguages) ? $defaultLanguage : $allowedLanguages[0]);

        // Visiting the root? Redirect to the default language index
        $this->match('GET|POST|PUT|DELETE|HEAD', '/', function() {
            header('location: /' . $this->defaultLanguage);
            exit();
        });

        // Create a before handler to make sure the language checks out when visiting anything but the root.
        // If the language doesn't check out, redirect to the default language index
        $this->before('GET|POST|PUT|DELETE|HEAD', '/([a-z0-9_-]+)(/.*)?', function($language, $slug = null) {

            // The given language does not appear in the array of allowed languages
            if (!in_array($language, $this->allowedLanguages)) {
                header('location: /' . $this->defaultLanguage);
                exit();
            }

        });

    }

}

// Create a Multilingual Router. It will automatically validate the language parameter in the URL. If that fails it will redirect to the default language.
$router = new MultilangRouter(
    ['en','nl','fr'], //= allowed languages
    'nl' // = default language
);

$router->get('/([a-z0-9_-]+)', function($language) {
    exit('This is the ' . $language . ' index');
});

$router->get('/([a-z0-9_-]+)/([a-z0-9_-]+)', function($language, $slug) {
     exit('This is the ' . $language . ' version of ' . $slug);
});

$router->get('/([a-z0-9_-]+)/(.*)', function($language, $slug) {
     exit('This is the ' . $language . ' version of ' . $slug . ' (multiple segments allowed)');
});

// Thunderbirds are go!
$router->run();

(Note that I have defined the pattern for the language as being [a-z0-9_-]+instead of\w{2}as this pattern will also catch requested to/whatever for example, enforcing a redirect to the default language.)

(Also note that one can still use the mount() function if needed, in case that first [a-z0-9_-]+in each pattern might be too overwhelming for one. But that brings us back to the very first suggested fix: just usemount())

The MultilangRouter works fine with these URLs:

  • /nl"This is the nl index"
  • /en"This is the en index"
  • /fr"This is the fr index"
  • /nl/foo"This is the nl version of foo"
  • /en/foo"This is the en version of foo"
  • /fr/foo"This is the fr version of foo"
  • /nl/foo/bar"This is the nl version of foo/bar (multiple segments allowed)"
  • /en/foo/bar"This is the en version of foo/bar (multiple segments allowed)"
  • /fr/foo/bar"This is the fr version of foo/bar (multiple segments allowed)"

It also redirects nicely in case a faulty URL (viz. one with an invalid $language is entered):

  • / → redirect to /nl
  • /de → redirect to /nl
  • /de/foo → redirect to /nl
  • /de/foo/bar → redirect to /nl

Regards,
B!

from router.

tleb avatar tleb commented on May 19, 2024

I adapted my class to what @ncaneldiee wanted to do: execute a function which has as a parameter the language, then create routes as normal.

As you showed in your example, their are different ways of implementing a multilingual router. One could be what @ncaneldiee is looking for, another one could be the one you showed and I am sure many other ways could be found.

Of course, no solution is perfect, they all have their advantages and drawbacks.

The reason I wanted to change the visibility of Router::handle() to protected is because it is required to pass a callable from Router::run() to Router::parseLang() (in my example). Here is the path it would take:

Router::run()
Router::handle()
Router::getCurrentUri()
Router::parseLang()

I know this seems weird, but, in a multilingual router, passing a callable to the run method which would get the language seems pretty logic (to me).

// could be used to set some value in the configuration, for example
$router->run(null, 'set_lang_config');

This is just an example which (to me) shows that most/every methods in a non-final class should be set to protected, so that the class behaviour can be adjusted to the needs and requirements of each user of the package.

Of course, this is just my opinion, and I'll just remove the concerned commit from #19 if you disagree.

from router.

ncaneldiee avatar ncaneldiee commented on May 19, 2024

@tleb idea about the custom router class is very nice (i will use this way), but i not too sure about changing the visibility of handle() method

Maybe seems to be good if add @bramus example in readme.md as an example of implementing a multilingual router, very easy to understand

Many thanks all

from router.

tleb avatar tleb commented on May 19, 2024

To be honest, I don't see the problem of setting handle() to protected. I now (since I had problems with named constructors and inheritance) put the methods I was putting in private in protected. protected simply allows the addition of optional parameters, which is something that doesn't go against the L of SOLID.

from router.

bramus avatar bramus commented on May 19, 2024

Hi @tleb. What I'm still missing to understand your example completely is the altered Router::run() method (viz. the second argument). Could you post the complete code of the altered versions (some branch, or a gist)?

from router.

tleb avatar tleb commented on May 19, 2024

Ok, I got lost in my own explanation, and ended up saying something completely wrong. Sorry I was blocked in my idea.

What I was thinking about would need to a complete rewriting of the class.

I'll remove the concerned commit.

from router.

bramus avatar bramus commented on May 19, 2024

Heh, have that too sometimes :-)

from router.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.