How to Represent a PHP Closure in YAML? - php

I'm developing a PHP application using Silex and YAML.
Now I want to represent a PHP closure using the YAML language concept. What's the best way? There's a way to do that?
The following code is an example of what I want to "translate" to YAML.
'users' => function () use ($app) {
return new UserProvider();
}
Thanks!

This is what Symfony does: you first define your User Provider as a service. With Silex you can do it with:
$app['my_user_provider'] = function () use ($app) {
return new UserProvider();
};
Then, in your yaml configuration, you pass the service id that you want use. Something like:
security:
user_provider: my_user_provider

Related

Create a Cache Provider class in Symfony

We have memcache on our Symfony 3.4 app:
cache:
app: cache.adapter.memcached
default_memcached_provider: "%app.memcached.dsn%"
However, we've been asked to use several cache servers, so just passing one DSN is no good.
Looking here (https://symfony.com/blog/new-in-symfony-3-3-memcached-cache-adapter), I see you can create it in code like this:
$client = MemcachedAdapter::createConnection(array(
// format => memcached://[user:pass#][ip|host|socket[:port]][?weight=int]
// 'weight' ranges from 0 to 100 and it's used to prioritize servers
'memcached://my.server.com:11211'
'memcached://rmf:abcdef#localhost'
'memcached://127.0.0.1?weight=50'
'memcached://username:the-password#/var/run/memcached.sock'
'memcached:///var/run/memcached.sock?weight=20'
));
However, that isn't autowired.
I believe we need to either make a provider class, or somehow get it to make calls to addServer($dsn), once instantiated. I also saw the following on random posts:
memcache:
class: Memcached
calls:
- [ addServer, [ %app.memcached.dsn.1% ]]
- [ addServer, [ %app.memcached.dsn.2% ]]
However it isn't really helping or I have missed something out.
Can anyone help? How do I create this provider class?
You can copy above code snippet as a service configuration to your services.yaml, which probably roughly looks like this:
# app/config/services.yaml
services:
app.memcached_client:
class: Memcached
factory: 'Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection'
arguments: [['memcached://my.server.com:11211', 'memcached://rmf:abcdef#localhost']]
app.memcached_adapter:
class: Symfony\Component\Cache\Adapter\MemcachedAdapter
arguments:
- '#app.memcached_client'
Then in your configuration you should be able to reference the adapter using the client created by the factory, e.g. something like:
# app/config/config.yaml
framework:
cache:
app: app.memcached_adapter
You might also be able to overwrite the default alias cache.adapter.memcached instead of having your own adapter.
Your approach using Memcached::addServer might work as well, but just like with MemcachedAdapter::createConnection this will return the Client, which needs to be passed to the cache adapter. That's why there is a second service app.memcached_adapter, which is used in the cache configuration.
Please be aware that I have not tested this, so this is rather a rough outline than a fully working solution,
For one of my projects running Symfony 3.4 the configuration was simpler:
Create a service that will be used as a client:
app.memcached_client:
class: Memcached
factory: ['AppBundle\Services\Memcached', 'createConnection']
arguments: ['memcached://%memcache_ip%:%memcache_port%']
The AppBundle\Services\Memcached can have all the custom logic I need like so:
class Memcached
{
public static function createConnection($dns)
{
$options = [
'persistent_id' => 'some id'
];
// Some more custom logic. Maybe adding some custom options
// For example for AWS Elasticache
if (defined('Memcached::OPT_CLIENT_MODE') && defined('Memcached::DYNAMIC_CLIENT_MODE')) {
$options['CLIENT_MODE'] = \Memcached::DYNAMIC_CLIENT_MODE;
}
return \Symfony\Component\Cache\Adapter\MemcachedAdapter::createConnection($dns, $options);
}
}
And then I used that service in my config.yml:
framework:
cache:
default_memcached_provider: app.memcached_client

Lumen/Dingo API Dynamic Versioning

I'm using Lumen for my project, currently the way I version my API is through prefixing and using a specific corresponding controller like so:
$api->get('/v1/users', 'App\Api\V1\Controllers\UserController#show');
$api->get('/v2/users', 'App\Api\V2\Controllers\UserController#show');
I want to change this, such that I take an argument from the user and use a controller based on that parameter.
This Route:
$api->get('/v{api_version}/users'...
Should use this controller:
'App\Api\V{api_version}\Controllers\UserController#show'
I'm currently using Dingo along side Lumen, is there anyway to do this with either Lumen or Dingo?
Yes, you can. But it's a little bit more complicated than in your example, but it's still a one-liner. Just define a closure and invoke your controller within it instead of passing the FQCN controller name directly.
routes/web.php
$app->get("api/v{version}/users", function ($version) use ($app) {
return $app->make("App\Api\V{$version}\Controllers\UserController")->show();
});
If someone else is interested (as I was) how to achieve the same in an laravel installation: Just use the method Controller::callAction() after the controller was resolved
Route::get("api/v{version}/test", function ($version) {
return app()->make('App\Api\V{$version}\Controllers\UserController')->callAction("show", [/* arguments */]);
});

How to find the route param from the top level middleware in Slim

I'm trying to accomplish API versioning of the APIs I've written using Slim framework.
All my versioned APIs look like this:
$app->get('/:version/book/search', function() {...});
I'm trying to create an application wide Route Condition for this version as follows:
\Slim\Route::setDefaultConditions(array(
'version' => 'v[3-6]'
));
So only the APIs with version number v3,v4,v5 and v6 should be allowed to get it.
My requirement is to store the exact version of the API call made in $app->version, and then do version specific code changes if needed for that. I have created a middleware which I added to the $app itself, so it gets executed for each API calls:
$app->add(new \GetVerMiddleware());
class GetVerMiddleware extends \Slim\Middleware
{
public function call()
{
// HOW TO GET THE version route parameter??
// ????
$app->version = $version;
$this->next->call();
}
}
So I want to know how to get the route parameter version inside the GetVerMiddleware. Is it even possible to get that? I know how to get the entire route printed (link), but I'm interested only in the version parameter.
OK, I've figured out the solution after some researching, the following link particularly helped:
Slim Framework forum
$app->add(new \GetVerMiddleware());
class GetVerMiddleware extends \Slim\Middleware
{
public function call()
{
$this->app->hook('slim.before.dispatch', array($this, 'onBeforeDispatch'));
$this->next->call();
}
public function onBeforeDispatch()
{
$route_params = $this->app->router()->getCurrentRoute()->getParam('version');
$this->app->version = $version;
}
}
I think the solution was pretty much there, apologies!

More route services in php

is theoretically possible use more services for routing?
For example if somebody use Silex and has this code:
$app = new Silex\Application();
$app->get('/test/{id}', function ($id) {
// ...
});
$app->run();
And i create api using Slim like that:
$app = new \Slim\Slim();
$app->get('/api/' . $version . 'something', function () use ($app){
$data = $app->request->params();
});
$app->run();
How user could implement my API withou rewrite Slim route function to Silex route function?
Thank you very much.
Giving a quick though, the way I see it you have 3 options:
Refactor controller closures to a named function
Both, Silex and Slim[1] can use any form of callable, so instead of passing a closure just pass a function name (or an array with class name, method name) or any other callable. This way with 1 declaration you'll be able to call it from both Slim and Silex (yes, you have to define the routes on both sides).
This has its drawbacks as the controller signature is different for the 2 frameworks, so you'll need to hook into silex flow and change the parameters (you have the Kernel.controller event to do that).
Also you'll need to redefine all your services and use the container wisely (i.e, don't use it as a service locator in your controllers).
This is probably the most inflexible way.
Define the routes in Silex and instatiate a Slim APP inside to call and return that
You'll need to define the api routes again in Silex and in each route you can try to instatiate the Slim APP (using something as require "path/to/the/slim/app/php/file.php") and then force the run method with the silent option on[2]
You'll probably need to refactor your Slim application a bit, you'll have to define your app in a file and call the run method in another.
Create a Silex middleware to handle all the /api/ calls
The easiest (?) way that I can think of is redefine the second option and create a catch all incoming request to the /api/ mount point by creating a Silex middleware that checks for each incoming request if the request has the /api/ string inside the request. Then you'll simply forawrd the request to the Slim application as I've told you in the second option.
Using this method you don't need to redefine the routes in Silex as everything under the /api/ point will be forwarded to the Slim application.
NOTE
In all cases you'll probably need to transform the response given from Slim to a Symfony response. Slim 3 uses the new PSR-7 HTTP interface so you have a solution here already. Slim 2 echos the response directly so you'll need to catch that and put it inside a Symfony response (new Response(ob_get_clean())
[1] This is for the Slim3, Slim2 may also be able to do that but I'm not sure (and the signature is different!)
[2] Again this is for Slim3, Slim2 does not has this option, so you'll need to figure something to get the response (maybe ob_get_clean()?)

Using Symfony with Backbone.js

I wanted to get views from the symfony community about how one would achieve using backbone.js to create a one paged application.
Is it even possible to call the functions in symfony (e.g public function executeCreate()) through backbone.js) has anyone done it?
Can you give me some resources? thanks in advance.
Why not? Symfony easily can return JSON data on request by backbone clientside app. For Symfony2 it can be like this:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyController extends Controller {
public function createAction($requestParam1, $requestParam2)
$response = new Response(json_encode(array(
'requestParam1' => $requestParam1,
'requestParam2' => $requestParam2
)));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
I would recommend you to have a look at the FOSRestBundle. Another attempt could be to handle the requests yourself, and return the date from the orm/odm via Serializer
the only thing bothered me so far, was the form handling (used backbone-forms), where I simply not was able to keep my code DRY (had duplicate data for the form+validation in symfony and in the frontend-part).
If you want to start a new Symfony2 + backbone app, I would recommend this bundle:
https://github.com/gigo6000/DevtimeBackboneBundle
There's also a demo app that can be helpful to see the backbone.js and Symfony2 interaction:
https://github.com/gigo6000/DevtimeRafflerBundle

Categories