Using container->get("GET.id) to get a value

My previous framework had some pretty snazzy conviniance methods. one of thsoe was ->get() being rather powerful. the entire system used a sort of “hive” structure. so ->get() brought values from that. (->get(“GET.id”) ->get(“POST.username”) ->get(“SESSION.sid”) etc.

i really want to replicate this ability in slim.

so my Q is… if i create my own container with this type of thing included, what are the issues i could face with it? (not being able to swap out di containers for instance)

the Container isnt as decent as i would want it, right now just going to POC on this.

thoughts?


// Init the container
$container = new Container(array(
    "ROUTE"=>null,
    "ALIAS"=>null,
    "PARAMS"=>array(),
    "GET"=>$_GET,
    "POST"=>$_POST,
));
// setting route middleware to populate the params / route parts in the container
$app->add(function (Request $request, RequestHandler $handler) {
    $routeContext = RouteContext::fromRequest($request);
    $route = $routeContext->getRoute();

    // return NotFound for non existent route
    if (empty($route)) {
        throw new HttpNotFoundException($request);
    }

    $name = $route->getName();
    $groups = $route->getGroups();
    $methods = $route->getMethods();
    $arguments = $route->getArguments();


    $this->set('PARAMS',$arguments);
    $this->set('ROUTE',$route);
    $this->set('ALIAS',$name);

    return $handler->handle($request);
});

usage

$app->get('/[{test}]', function ($request, $response) {
    var_dump($this->get("GET.id"));
    var_dump($this->get("PARAMS.test"));
})

and lastly the Container itself.

the basics are if the key being provided doesnt exist then split the “key” on . and see if the base key exists. if the base key is an array then transverse that element in the container by dot notation.

<?php

declare(strict_types=1);

namespace system\container;

use Closure;
use LogicException;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use function array_key_exists;
use function call_user_func_array;
use system\utilities\Arrays;

class Container implements ContainerInterface {
    /**
     * @var array
     */
    private $container;


    /**
     * @param array $entries Array of string => mixed.
     */
    public function __construct(array $entries = []) {
        $this->container = $entries;
    }



    private function cut($key) {
        return preg_split('/\[\h*[\'"]?(.+?)[\'"]?\h*\]|(->)|\./',
            $key, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
        );
    }
    /**
     * @param string $key magic method for ->get
     */
    public function __get(string  $key) {
        return $this->get($key);
    }
    /**
     * {@inheritdoc}
     */
    public function get(string $key) {
        $args = func_get_args();
        reset($args);

        if (!$this->has($key)){
            $parts = $this->cut($key);
            $key_base = array_shift($parts);


            if ($this->has($key_base)){
                if (is_array($this->container[$key_base])){
                    return Arrays::getValueByKey(implode(".",$parts),(array)$this->container[$key_base]);
                }
                return call_user_func_array($this->container[$key_base], [$this, ...$args]);
            }

        }
        if (!$this->resolved($key)) {
            $this->container[$key] = call_user_func_array($this->container[$key], [$this, ...$args]);
        }

        return $this->container[$key];

    }


    /**
     * {@inheritdoc}
     */
    public function has(string $key): bool {
        return array_key_exists($key, $this->container);
    }


    /**
     * @param string $key
     * @param mixed $entry
     */
    public function set(string $key, $entry): void {
        $this->container[$key] = $entry;
    }


    /**
     * Returns whether a given service has already been resolved
     * into its final value, or is still a callable.
     *
     * @throws NotFoundExceptionInterface No entry was found for **this** identifier.
     */
    public function resolved(string $key): bool {
        if (!$this->has($key)) {
            throw new class extends LogicException implements NotFoundExceptionInterface {
            };
        }

        return !$this->container[$key] instanceof Closure;
    }
}

My first thought is that the DI container was not invented for this purpose and should not be used that way.

I think a clean refactoring would be a much better choice in the long run.

For example.

Old:

$app->get('/[{test}]', function ($request, $response) {
    echo $this->get("GET.id");
});

New:

$app->get('/[{test}]', function ($request, $response) {
    echo $request->getQueryParams()['id'];
});

Old:

$app->get('/[{test}]', function ($request, $response) {
    echo $this->get("PARAMS.test");
});

New:

$app->get('/[{test}]', function ($request, $response, $args) {
    echo $args['test'];
});

the issue i have with that is that if for instance you use ‘/[{test}]’ you now have to add in extra code to handle it as well echo $args['test'] ?? null; i feel slim tries too hard to follow the “specs” too hard and less usefull for the devs (why the hell isnt there a $request->getQueryParam(“id”) for instance but there is a $request->getQueryParams() when you could just as easily do echo $_GET[‘id’] - shorter for 1? - i get it that its “the spec” but whoa…

just cause the DI container isnt invented for this purpose, is it blatantly wrong? items in the container if you see a container item as a “service” then we could stretch it to be “the container holds system variables” or whatever they are called ($_GET / $_POST etc)

guessing something like $this->system->get("GET.id") then? where “system” is a container item that can do the GET / POST / REQUEST etc. this actually seems more clean then and to just inject $system in via DI. - bleh seems php-di doesnt let you do $this->system->… only lets $this->get(“system”)->… im sure 1 of the container let you do it this way but cant recall which :frowning:


also the argument of being able to switch out DI containers as you want… well how often would you actually do that with a project (just cause you can doesnt mean you will or should). and using php di for instance… the recommended way is to use slim-bridge in any case which locks you in to a “vender”.


im trying to better my projects at the moment… been a php dev for quite some time. so obviously old dog new tricks at this point :frowning: im probs gonna still be asking lots of questions here lol

right… new version… (i cant get use Slim\Csrf\Guard; to work with \DI\Bridge\Slim\Bridge tho :frowning:

$container->set(System::class, function () use ($package) {
    return new System([
        "VERSION"=>$package->version,
        "PACKAGE"=>$package->description,
    ]);
});

and usage

$app->get('/[{test}]', function ($request, $response, System $system) {
    var_dump($system->get("VERSION"));
    var_dump($system->get("GET.id"));
    var_dump($system->get("PARAMS.test","fish"));
    $response->getBody()->write("hi");
    return $response;
})->setName("home");

still beefing out System tho so not ready to share yet. (System has get set ie function get($key,$default=null){


i understand the concept of composition, but it still feels really strange that $this is the container and using the container is bad. seems like such a waste of “shorter code” lol

Yes, it’s still not a good idea to use the DI container for context specific information. For this reason, the Request and Response object has been removed from the DI container in Slim 4. Variables like $_GET / $_POST are super-globals and global variables are bad as we know. So today it makes no sense to use them anymore and better make use a standard interfaces like PSR-7 and PSR-15.
Just a tip: Try to keep it simple and use the request object directly when possible. It’s not about “short code”, it about standard interfaces, modern/clean OOP and testable code.

I would recommend reading my Slim 4 Tutorial, that contains a lot of information about the DI container, Action handler (classes) and dependency injection.

the “fix” here was

$responseFactory = $app->getResponseFactory();
$container->set("csrf", function () use ($responseFactory) {
    return new Guard($responseFactory);
});
$app->add($container->get("csrf")); // $container->get("csrf") part.. doesnt work just using $app->add("csrf") like a "normal" DI container. (this is with slim-bridge di thing)

i have. very very informative blog posts! thank you!. thing is im planning on moving my code bases over to slim so i need to get “my” environment setup as i will need it which is what im doing now. some things i like from other frameworks (microframeworks - mainly fatfree but the way f3 does things just feels like a loosing battle with “modern” coding practices) and desperately want to implement this side which takes thing a teeny bit past starting a project from scratch. (like this being able to use a single entry for getting various data. that way i can serialize all the input in a single place and not ever have to worry about it again - like stripping tags from a post field etc)

your comment earlier about not using a contianer like that actually got me thinking instead of duct taping the container to work as i need it i might as well build a system “dependency” and beef it out nicely with stuff i need it to do. keeps all things happy then. thank you.

oops, nvm. the dependency needs to know “some” info after all… and with testing it makes sense that we need to pass the definition into it in the setup part