If I do a post, I can get content by doing $payload = json_decode($app->request->getBody());
But I cannot understand how request->getBody works in slim.
First, there's a magic method :
public function __get($name)
{
return $this->container->get($name);
}
This will return a Slim\Http\Request object. That's fine for now.
$this->container is the Slim\Helper\Set, and this is the get function :
public function get($key, $default = null)
{
if ($this->has($key)) {
$isInvokable = is_object($this->data[$this->normalizeKey($key)]) && method_exists($this->data[$this->normalizeKey($key)], '__invoke');
return $isInvokable ? $this->data[$this->normalizeKey($key)]($this) : $this->data[$this->normalizeKey($key)];
}
return $default;
}
$this->data[$this->normalizeKey($key)] is the same as $this->data['request'], which is something of type "Closure" (not sure to understand this).
$isInvokable is true, so this is called :
$this->data[$this->normalizeKey($key)]($this)
What is this line doing ? Why the ($this) (Slim\Helper\Set) at the end ?
Especially, why the next function to be called is this :
public function singleton($key, $value)
{
$this->set($key, function ($c) use ($value) {
static $object;
if (null === $object) {
$object = $value($c);
}
return $object;
});
}
Why singleton($key, $value) ?
It has never been called !
$key is not defined at the start of the function. Also what makes $c a Slim\Helper\Set, and $value a closure ?
And why the execution of only static $object makes the $object goes from unitialized to one of type Slim\Http\Request ?
Disclaimer: I'm not familiar with Slim. I'm just going by what you've posted.
Well, the Set->get() method tests, if the value in the data property array with the key $key can be invoked, and then does it, if true.
So $this->data[$this->normalizeKey($key)]($this) is calling a method with $this given as parameter and then Set->get() returns that method's return value.
A closure is also often called an "anonymous function", which is a new feature as of PHP 5.3. Using an array as variable to call a function is available since PHP 5.4.
It allows you to pass around functions/methods as values, which is arguably the distinctive feature in functional programming.
Function singleton is called before in the initialization.
It is setting all $key to a function.
So, $this->data[$this->normalizeKey($key)]($this) is that function !
Related
I have a callable $f and I would like to know if it can receive an instance of a certain class Foo as input.
At the moment I'm doing something like
try {
$f($foo);
} catch (\TypeError $e) {
throw new \InvalidArgumentException('The provided function can not evaluate inputs of this type');
}
Is there a way to check this WITHOUT actually invoking the callable? Maybe with reflection or some other dark magic?
If you want to be able to reflect any kind of callable, you'll need to wrap up the logic in a small function. Depending on whether you've got an array, a function name or an anonymous function, you need to create either a ReflectionFunction or ReflectionMethod. Fortunately, these both extend ReflectionFunctionAbstract, so we can type-hint the return value.
function reflectCallable($arg): ReflectionFunctionAbstract {
if (is_array($arg)) {
$ref = new ReflectionMethod(...$arg);
} elseif (is_callable($arg)) {
$ref = new ReflectionFunction($arg);
}
return $ref;
}
This will return you the appropriate object for your callable value, which you can then use to fetch the parameters and act accordingly:
function definedFunc(Foo $foo) {}
$callable = function(Foo $foo) {};
class Bar { public function baz(Foo $foo) {} }
foreach (['definedFunc', $callable, ['Bar', 'baz']] as $callable) {
$reflected = reflectCallable($callable);
if ((string) $reflected->getParameters()[0]->getType() === 'Foo') {
echo 'Callable takes Foo', PHP_EOL;
}
}
See https://3v4l.org/c5vmM
Note that this doesn't do any error handling - you'll probably get warnings/notices if the callable doesn't take any parameters or the first parameter doesn't have a type. It also requires PHP 7+, but hopefully that's not an issue.
It doesn't currently support objects that implement __invoke or static calls defined as "Foo::bar", but they wouldn't be too hard to add if necessary. I've just found something very similar in the source of Twig, which does a more thorough job: https://github.com/twigphp/Twig/blob/v2.8.0/src/Node/Expression/CallExpression.php#L280
You can with ReflectionParameter::getType:
$f = function(Foo $foo) {};
$reflectionFunc = new ReflectionFunction($f);
$reflectionParams = $reflectionFunc->getParameters();
$reflectionType1 = $reflectionParams[0]->getType();
echo $reflectionType1;
output:
Foo
In Laravel, I know that
return Redirect::back()->with(['Foo'=>'Bar']);
is equivalent to
return Redirect::back()->withFoo('Bar');
But... how does it work? I mean, creating a new function withFoo on the fly to pass a variable? Where is this behaviour defined inside the Laravel code? Where can I check it?
Here's how it's implemented (source):
public function __call($method, $parameters)
{
if (Str::startsWith($method, 'with')) {
return $this->with(Str::snake(substr($method, 4)), $parameters[0]);
}
throw new BadMethodCallException("Method [$method] does not exist on Redirect.");
}
Remember, magic method __call is triggered when one attempts to invoke a method otherwise inaccessible. The first parameter is the method's name, followed by parameters passed. In this particular case, RedirectResponse->with() is triggered, setting up flash data:
public function with($key, $value = null)
{
$key = is_array($key) ? $key : [$key => $value];
foreach ($key as $k => $v) {
$this->session->flash($k, $v);
}
return $this;
}
I am trying to study the code for Slim framework. In the constructor for Slim class, $c is passed to the closure (for instance, when storing/setting the request/response object in the container):
public function __construct(array $userSettings = array())
{
// Setup IoC container
$this->container = new \Slim\Helper\Set();
$this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings);
// Default request
$this->container->singleton('request', function ($c) {
return new \Slim\Http\Request($c['environment']);
});
// Default response
$this->container->singleton('response', function ($c) {
return new \Slim\Http\Response();
});
But $c is not defined/declared anywhere prior to this statement, so how does it work? I started to trace everything from the beginning and I can't find $c anywhere prior to it being used in this manner.
$c is a parameter of the closure function. Imagine you had a function by itself:
function myFunction($c) {
echo $c;
}
In the case of a closure, you can store an anonymous function in a variable:
$someFunction = function ($c) {
echo $c;
}
$someFunction("hello world");
So instead of directly storing the closure into the variable, the code above is passing the anonymous function as a parameter to $this->container->singleton(). So $c is not created until the closure is called. The singleton method stores this in a variable called $value, so if that function ran:
$value(array('environment'=>'test'));
$c would now contain array('environment'=>'test')
Slim also uses the __get() __set() magic methods quite a bit, so from the example code you set, within the Slim class, one could call:
$request = $this->container->request(array('environment'=>'test'));
The container is of class Slim\Helper\Set. Since this doesn't have a request method, this would call the container's __get() method. It would look up the stored method configured above for 'request' and pass the array in as $c
I think khartnett gave a perfect answer. To make it clearer for you, an example.
When you define a function, you are only describing how it works. You are not setting any specific values. For example, when I write:
function sum($a, $b) {
return $a + $b;
}
I am not saying what the values of $a and $b are here. I am just describing what I am doing with these variables to calculate a result. It is not until I call this function that I'm working with actual values:
sum(3, 4); // returns 7
In your question, the $c variable is like the $a and $b variable.
Like khartnett showed in his answer, it works like this:
// Definition time
$someFunction = function ($c) {
echo $c;
}
// Calling time
$someFunction("hello world");
It is not until calling time that $c gets its value (in this example, the value is "hello world").
The $c is a reference to the container itself - so that any dependencies will be automatically resolved when invoked.
So using for example the request object:
// Default request
$this->container->singleton('request', function ($c) {
return new \Slim\Http\Request($c['environment']);
});
Looking at the Request constructor you will see that it expects an instance of Environment, which we just told the container should be available already using the key 'environment'.
The answer will lay in $app->run():
$this->container->get("service_name") then in get() method offsetGet($id)
public function offsetGet($id)
{
if (!isset($this->keys[$id])) {
throw new UnknownIdentifierException($id);
}
if (
isset($this->raw[$id])
|| !\is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !\method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}
$raw = $this->values[$id];
$val = $this->values[$id] = $raw($this); // THIS LINE CALLS THE CONTAINER ITSELF
$this->raw[$id] = $raw;
$this->frozen[$id] = true;
return $val;
}
Pimple Container functioning it's a bit more complicated due to the fact:
Allowing any PHP callable leads to difficult to debug problems
as function names (strings) are callable (creating a function with
the same name as an existing parameter would break your container)
So unique identifiers are introduced.
Here is a simple implementation of ServiceContainer get() method inside the ServiceContainer class:
public function get($serviceName)
{
if (!array_key_exists($serviceName, $this->container)) {
throw new \http\Exception\InvalidArgumentException("Service not found!");
}
$service = $this->container[$serviceName];
if (is_callable($service)) {
$this->container[$serviceName] = $service = $service($this);
// $this (aka ServiceContainer) will be passed as parameter to Closures
}
return $service;
}
Hope it clarifies the question, have a nice day!
I'm trying to dynamically create the base for a DB entity generalization for a project I'm working on. I basically want to dynamically create a set of standard methods and tools for the properties in any class that extends this. Much like the tools you get for free with Python/Django.
I got the idea from this guy: http://www.stubbles.org/archives/65-Extending-objects-with-new-methods-at-runtime.html
So I've implemented the __call function as described in the post above,
public function __call($method, $args) {
echo "<br>Calling ".$method;
if (isset($this->$method) === true) {
$func = $this->$method;
$func();
}
}
I have a function which gives me the objects public/protected properties through get_object_vars,
public function getJsonData() {
$var = get_object_vars($this);
foreach($var as &$value) {
if (is_object($value) && method_exists($value, 'getJsonData')) {
$value = $value->getJsonData;
}
}
return $var;
}
and now I want to create some methods for them:
public function __construct() {
foreach($this->getJsonData() as $name => $value) {
// Create standard getter
$methodName = "get".$name;
$me = $this;
$this->$methodName = function() use ($me, $methodName, $name) {
echo "<br>".$methodName." is called";
return $me->$name;
};
}
}
Thanks to Louis H. which pointed out the "use" keyword for this down below.
This basically creates an anonymous function on the fly. The function is callable, but it is no longer within the context of it's object. It produces a "Fatal error: Cannot access protected property"
Unfortunately I'm bound to PHP version 5.3, which rules out Closure::bind. The suggested solution in Lazy loading class methods in PHP will therefore not work here.
I'm rather stumped here... Any other suggestions?
Update
Edited for brevity.
Try it like this (you have to make the variables you'll need available to the method)
$this->$methodName = function() use ($this, $methodName, $name){
echo "<br>".$methodName." is called";
return $this->$$name;
};
You should have access to the object context through $this.
Instead of updating the original question above, I include the complete solution here for anybody struggling with the same issues:
First of all, since the closure cannot have real object access, I needed to include the actual value with the "use" declaration when creating the closure function (see original __construct function above):
$value =& $this->$name;
$this->$methodName = function() use ($me, $methodName, &$value) {
return $value;
};
Secondly the __call magic method did not just need to call the closure function, it needed also to return any output from it. So instead of just calling $func(), I return $func();
This did the trick! :-)
I sometimes have variables that might not be set and I would like to use a default parameter instead. Like here:
if ($p == "a") doSomething();
If $p is not defined PHP throws Notice: Undefined variable. To avoid this I often I used this construct in such a case:
$p = (isset($p) ? $p : "");
But that is ugly if you have to use it a lot. So I wrote a function for it:
function getIfSet(&$value, $default = '')
{
return isset($value) ? $value : $default;
}
// Example
if (getIfSet($p) == "a") doSomething();
I wonder if there is a PHP function for this or how you solve this.
Just a little improvement, prefer passing null value to $default, passing empty string can be confusing, cause correct value can be empty string.
function getIfSet(&$value, $default = null)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($p);
isset() is about as clean as it gets. Although I must admit that I'm not too fond of defaulting to an empty string, simply because a variable could be an empty string, yet still "be set". I think that a default of bool false or null would be truer to the behavior of isset:
function getIfSet(&$value, $default = false)
{
return isset($value) ? $value : $default;
}
$p = getIfSet($p);
if($p !== false){
//insert or whatever
}
else{
header('Location: error.php');
exit;
}
Depending on what kind of values you're checking (maybe REQUEST data?), consider using classes. They are fun and they could be available anywhere.
Assuming you're checking POST data (if you don't, well, take this as an idea), create a class that checks this array:
class Post
{
public function __get($index)
{
if (isset($_POST[$index]))
return $_POST[$index];
else
return null;
}
}
As simple as that. You know that __get() will trigger when you try to access a non-existant property. In this case, if the property (actually, the index in the $_POST array) doesn't exist, null will be returned and no errors are generated.
Now you can do:
$params = new Post();
$foo = $params->name ?: ''; // of course this doesn't make much sense.
if (!$params->password) ...
// instead of
if (isset($_POST['password'])) ...
// you'll still have to use isset for cases like:
if (isset($_POST['user']['password']) ...
if (isset($params->user['password'])) ...
// but still looks neater I'd say
A problem you'll find soon is that $params isn't a super global variable, while $_POST are. How to solve this? Create it in the constructor of your controller class and use Dependency Injection for all other objects your are using.
I tried to make renocor's answer more clean and OOP when I came up with this solution:
class NiceArray implements ArrayAccess {
protected $array;
public function __construct(&$array) {
$this->array =& $array;
}
public function offsetExists($offset) {
return true;
}
public function offsetGet($offset) {
if (isset($this->array[$offset]))
{
return $this->array[$offset];
}
else
{
return null;
}
}
public function offsetSet($offset, $value) {
$this->array[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->array[$offset]);
}
}
Usage:
$get = new NiceArray($_GET);
if ($get['p'] == "a") doSomething();
I know the class is kind of big but this way you still have an array and you can easily use it for every array you want. You do not need to change any code you may had before. You can still access and change the data. It will even change the original array.