First off, some basic information about my project: I have a website built with Symfony 3. For some tasks I'm thinking about implementing to run asynchronous PHP methods. Some events use a lot of time but their results need not be immediately evident.
For instance: in method newOrder I have function addUserLTV who do few steps. The customer does not have to wait for all the steps to complete, only to get immediately the confirmation after the basic operation - 'newOrder' will add addUserLTV to queue and show immediately confirmation (finished run).
The queue tasks will be run when the server have time to do it.
public function addUserLTV( $userID, $addLTV )
{ //same code
}
How to do it? It is possible in symphony 3?
This is something you can easily do with enqueue bundle. Just a few words on why should you choose it:
It supports a lot of transports from the simplest one (filesystem) to enterprise ones (RabbitMQ or Amazon SQS).
It comes with a very powerful bundle.
It has a top level abstraction which could be used with the greatest of ease.
There are a lot more which might come in handy.
Regarding your question. Here's how you can do this with the enqueue bundle. Follow setup instructions from the doc.
Now the addUserLTV method will look like this:
<?php
namespace Acme;
use Enqueue\Client\ProducerInterface;
class AddUserLTVService
{
/**
* #var ProducerInterface
*/
private $producer;
/**
* #param ProducerInterface $producer
*/
public function __construct(ProducerInterface $producer)
{
$this->producer = $producer;
}
public function addUserLTV( $userID, $addLTV )
{
$this->producer->sendCommand('add_user_ltv', [
'userId' => $userID,
'ltv' => $addLTV]
);
}
}
It sends the message to a message queue using the client (top level abstraction I've mentioned before). The service has to be registered to the Symfony container:
services:
Acme\AddUserLTVService:
arguments: ['#enqueue.producer']
Now let look at the consumption side. You need a command processor that do the job:
<?php
namespace Acme;
use Enqueue\Client\CommandSubscriberInterface;
use Enqueue\Psr\PsrContext;
use Enqueue\Psr\PsrMessage;
use Enqueue\Psr\PsrProcessor;
use Enqueue\Util\JSON;
class AddUserTVAProcessor implements PsrProcessor, CommandSubscriberInterface
{
public function process(PsrMessage $message, PsrContext $context)
{
$data = JSON::decode($message->getBody());
$userID = $data['userID'];
$addLTV = $data['ltv'];
// do job
return self::ACK;
}
public static function getSubscribedCommand()
{
return 'add_user_ltv';
}
}
Register it as a service with a enqueue.client.processor tag:
services:
Acme\AddUserTVAProcessor:
tags:
- {name: 'enqueue.client.processor'}
That's it for coding. Run the consume command and you are done:
./bin/console enqueue:consume --setup-broker -vvv
Related
I'm writing a small API in Laravel, partly for the purposes of learning this framework. I think I have spotted a gaping hole in the docs, but it may be due to my not understanding the "Laravel way" to do what I want.
I am writing an HTTP API to, amongst other things, list, create and delete system users on a Linux server. The structure is like so:
Routes to /v1/users connect GET, POST and DELETE verbs to controller methods get, create and delete respectively.
The controller App\Http\Controllers\UserController does not actually run system calls, that is done by a service App\Services\Users.
The service is created by a ServiceProvider App\Providers\Server\Users that registers a singleton of the service on a deferred basis.
The service is instantiated by Laravel automatically and auto-injected into the controller's constructor.
OK, so this all works. I have also written some test code, like so:
public function testGetUsers()
{
$response = $this->json('GET', '/v1/users');
/* #var $response \Illuminate\Http\JsonResponse */
$response
->assertStatus(200)
->assertJson(['ok' => true, ]);
}
This also works fine. However, this uses the normal bindings for the UserService, and I want to put a dummy/mock in here instead.
I think I need to change my UserService to an interface, which is easy, but I am not sure how to tell the underlying test system that I want it to run my controller, but with a non-standard service. I see App::bind() cropping up in Stack Overflow answers when researching this, but App is not automatically in scope in artisan-generated tests, so it feels like clutching at straws.
How can I instantiate a dummy service and then send it to Laravel when testing, so it does not use the standard ServiceProvider instead?
The obvious way is to re-bind the implementation in setUp().
Make your self a new UserTestCase (or edit the one provided by Laravel) and add:
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function setUp()
{
parent::setUp();
app()->bind(YourService::class, function() { // not a service provider but the target of service provider
return new YourFakeService();
});
}
}
class YourFakeService {} // I personally keep fakes in the test files itself if they are short
Register providers conditionally based on environment (put this in AppServiceProvider.php or any other provider that you designate for this task - ConditionalLoaderServiceProvider.php or whatever) in register() method
if (app()->environment('testing')) {
app()->register(FakeUserProvider::class);
} else {
app()->register(UserProvider::class);
}
Note: drawback is that list of providers is on two places one in config/app.php and one in the AppServiceProvider.php
Aha, I have found a temporary solution. I'll post it here, and then explain how it can be improved.
<?php
namespace Tests\Feature;
use Tests\TestCase;
use \App\Services\Users as UsersService;
class UsersTest extends TestCase
{
/**
* Checks the listing of users
*
* #return void
*/
public function testGetUsers()
{
$this->app->bind(UsersService::class, function() {
return new UsersDummy();
});
$response = $this->json('GET', '/v1/users');
$response
->assertStatus(200)
->assertJson(['ok' => true, ]);
}
}
class UsersDummy extends UsersService
{
public function listUsers()
{
return ['tom', 'dick', 'harry', ];
}
}
This injects a DI binding so that the default ServiceProvider does not need to kick in. If I add some debug code to $response like so:
/* #var $response \Illuminate\Http\JsonResponse */
print_r($response->getData(true));
then I get this output:
Array
(
[ok] => 1
[users] => Array
(
[0] => tom
[1] => dick
[2] => harry
)
)
This has allowed me to create a test with a boundary drawn around the PHP, and no calls are made to the test box to interact with the user system.
I will next investigate whether my controller's constructor can be changed from a concrete implementation hint (\App\Services\Users) to an interface, so that my test implementation does not need to extend from the real one.
I have 3 functions that get json data from external apis and then saves in my database. Each function is its in own class e.g :
Class api1 {
public function fetch()
{
//Do Something
}
}
Class api2 {
public function fetch()
{
//Do Something
}
}
Since its api call might take some time or delay . I want to run all 3 in parallel so that api2 does not have to wait for api1 to complete.
Any way to do that ?
* Note : I'm also going to use laravel scheduler which will run each function every minute or run a single function containing all 3.
To me this looks more of like callback request for data, so to keep your app from not slowing down this should be a background job.
But before that I would implement an interface for those classes:
interface apiService{
public function fetch();
}
Class api1 implements apiService {
public function fetch()
{
//Do Something
}
}
Class api2 implements apiService{
public function fetch()
{
//Do Something
}
}
Create a job class php artisan make:job dataFetcher
Jobs will be structured under App\Jobs\
The job class in Laravel its dead simple, consisting of a constructor to Inject dependencies and handle() to fire the job.
protected $service;
public function __construct(apiService $service)
{
$this->service = $service;
}
public function handle()
{
$this->apiService->fetch();
}
Note that I am injecting the interface instead of concrete class, using a bit more high level code here. So now you can create a command to fire the calls with a cron job, or you can create a custom service provider to fire the commands as soon as app bootstraps.
I would go with a custom artisan command here:
So just create a custom artisan command on handle method
public function handle()
{
Job::dispatch(new FirstApiClass);
Job::dispatch(new SecondApiClass);
}
Handle method will execute first line and Job will be processed in background(doesnt matter if job failed or not), then next call will be fired and so on...
Note the use of the interface in this case, Job class doesnt really care which service you are calling as long as you provide an implmenetation of it.
I have the following definition:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\SomeClass;
class SomeProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}
public function provides()
{
die("This never gets called");
return [SomeClass::class];
}
}
And it returns an instance of SomeClass as expected, except that according to the documentation, if $defer is true then the provides() method should be called. No matter what I set $defer to, and no matter if I actually ask for an instance of SomeClass or not, provides() is never called.
The way I'm asking for an instance of the class is as follows:
App::make('SomeClass');
Short answer:
Your compiled manifest file is already compiled by framework.
On the first time when Laravel build the application (and resolves all of services providers in IoC container)
it writes to cached file named services.php (that is, the manifest file, placed in: bootstrap/cache/services.php).
So, if you clear the compiled via php artisan clear-compiled command it should force framework to rebuild the manifest file and you could to note that provides method is called.
On the next calls/requests provides method is not called anymore.
The sequence of framework boot is nearly like this:
//public/index.php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
\Illuminate\Foundation\Http\Kernel::__construct();
\Illuminate\Foundation\Http\Kernel::handle();
\Illuminate\Foundation\Http\Kernel::sendRequestThroughRouter();
\Illuminate\Foundation\Http\Kernel::bootstrap();
\Illuminate\Foundation\Application::bootstrapWith();
# where $bootstrapper is a item from \Illuminate\Foundation\Http\Kernel::$bootstrappers
# and $this is instance of \Illuminate\Foundation\Application
\Illuminate\Foundation\Application::make($bootstrapper)->bootstrap($this);
One of bootstrappers is Illuminate\Foundation\Bootstrap\RegisterProviders which
invokes \Illuminate\Foundation\Application::registerConfiguredProviders() and then invokes
\Illuminate\Foundation\ProviderRepository::__construct() and finally:
\Illuminate\Foundation\ProviderRepository::load()
When \Illuminate\Foundation\ProviderRepository::load() is called all services providers is registered and
\Illuminate\Support\ServiceProvider::provides() are called also well.
And here is the snippet you should know (from \Illuminate\Foundation\ProviderRepository::load):
/**
* Register the application service providers.
*
* #param array $providers
* #return void
*/
public function load(array $providers)
{
$manifest = $this->loadManifest();
// First we will load the service manifest, which contains information on all
// service providers registered with the application and which services it
// provides. This is used to know which services are "deferred" loaders.
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
// Next, we will register events to load the providers for each of the events
// that it has requested. This allows the service provider to defer itself
// while still getting automatically loaded when a certain event occurs.
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
// We will go ahead and register all of the eagerly loaded providers with the
// application so their services can be registered with the application as
// a provided service. Then we will set the deferred service list on it.
foreach ($manifest['eager'] as $provider) {
$this->app->register($this->createProvider($provider));
}
$this->app->addDeferredServices($manifest['deferred']);
}
\Illuminate\Foundation\ProviderRepository::compileManifest() is the place where your provides() method is performed.
After doing my own testing, it would seem this is an issue (maybe "issue" is a strong word in this case).
If you register something in a service provider which has the name of a class, Laravel is just going to return that class and disregard whatever is in the service provider. I started doing the same thing as you did....
protected $defer = true;
public function register()
{
$this->app->bind(SomeClass::class, function ($app)
{
return new SomeClass();
});
}
public function provides()
{
dd('testerino');
}
$test = \App::make('App\SomeClass');
And $test is an instance of SomeClass. However if I make the following change...
$this->app->bind('test', function ($app) { ... }
And use
$test = \App::make('test');
Then it hits the deffered function and outputs the text testerino.
I think the issue here is that Laravel knows you are just trying to grab a class. In this instance, there is no reason to register what you are trying to register with the container, you aren't doing anything except telling Laravel to make an instance of App\SomeClass when it should make an instance of App\SomeClass.
However, if you tell Laravel you want an instance of App\SomeClass when you call App::make('test'), then it actually needs to bind that class to test so then I think it starts to pay attention to the service provider.
I have a "standard procedure" that I need in EVERY route I call in Symfony.
I basically create a short (relatively) unique number, check the permission and log that the function has been called.
Here is one example:
**
* #Route("/_ajax/_saveNewClient", name="saveNewClient")
*/
public function saveNewClientAction(Request $request)
{
/* Create Unique TrackNumber */
$unique= $this->get('log')->createUnique();
/* Check Permission */
if (!$permission = $this->get('permission')->needsLevel(2, $unique)) {
/* Log Action */
$this->get('log')->writeLog('No Permission, 2 needed', __LINE__, 4);
return new JsonResponse(array(
'result' => 'error',
'message' => 'Insufficient Permission'
)
);
}
/* Log Action */
$this->get('log')->writeLog('called '.__FUNCTION__, __LINE__, 1, $unique);
return $this->render(':admin:index.html.twig', array());
}
Is there a way to put all that in one function somewhere?
The writeLog gets called at other parts in the functions as well, so I don't want to combine it with the permisson check, although that would be possible of course.
When creating an EventListener, do I still have to call it in every function or is it possible to have it automatically called?
Any hint appreciated!
You could try to make a beforefilter.
http://symfony.com/doc/current/cookbook/event_dispatcher/before_after_filters.html
How to Set Up Before and After Filters
It is quite common in web application development to need some logic to be executed just before or just after your controller actions acting as filters or hooks.
Some web frameworks define methods like preExecute() and postExecute(), but there is no such thing in Symfony. The good news is that there is a much better way to interfere with the Request -> Response process using the EventDispatcher component.
Token Validation Example
Imagine that you need to develop an API where some controllers are public but some others are restricted to one or some clients. For these private features, you might provide a token to your clients to identify themselves.
So, before executing your controller action, you need to check if the action is restricted or not. If it is restricted, you need to validate the provided token.
Please note that for simplicity in this recipe, tokens will be defined in config and neither database setup nor authentication via the Security component will be used.
Before Filters with the kernel.controller Event
First, store some basic token configuration using config.yml and the parameters key:
YAML
# app/config/config.yml
parameters:
tokens:
client1: pass1
client2: pass2
Tag Controllers to Be Checked
A kernel.controller listener gets notified on every request, right before the controller is executed. So, first, you need some way to identify if the controller that matches the request needs token validation.
A clean and easy way is to create an empty interface and make the controllers implement it:
namespace AppBundle\Controller;
interface TokenAuthenticatedController
{
// ...
}
A controller that implements this interface simply looks like this:
namespace AppBundle\Controller;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class FooController extends Controller implements TokenAuthenticatedController
{
// An action that needs authentication
public function barAction()
{
// ...
}
}
Creating an Event Listener
Next, you'll need to create an event listener, which will hold the logic that you want executed before your controllers. If you're not familiar with event listeners, you can learn more about them at How to Create Event Listeners and Subscribers:
// src/AppBundle/EventListener/TokenListener.php
namespace AppBundle\EventListener;
use AppBundle\Controller\TokenAuthenticatedController;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class TokenListener
{
private $tokens;
public function __construct($tokens)
{
$this->tokens = $tokens;
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
/*
* $controller passed can be either a class or a Closure.
* This is not usual in Symfony but it may happen.
* If it is a class, it comes in array format
*/
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof TokenAuthenticatedController) {
$token = $event->getRequest()->query->get('token');
if (!in_array($token, $this->tokens)) {
throw new AccessDeniedHttpException('This action needs a valid token!');
}
}
}
}
Registering the Listener
Finally, register your listener as a service and tag it as an event listener. By listening on kernel.controller, you're telling Symfony that you want your listener to be called just before any controller is executed.
YAML
# app/config/services.yml
services:
app.tokens.action_listener:
class: AppBundle\EventListener\TokenListener
arguments: ['%tokens%']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
With this configuration, your TokenListener onKernelController method will be executed on each request. If the controller that is about to be executed implements TokenAuthenticatedController, token authentication is applied. This lets you have a "before" filter on any controller that you want.
What I have is a symfony application, which contains some entities along with some repositories. A second non-symfony application should interface with the first one for interacting with some logic written in it (in this very moment just using the entities and their proper repositories).
Keep in mind that the first application could have its own autoload register etc.
I thought of an API class for external applications, which stays in the app directory. To use that the application should require a script. Here is the idea:
app/authInterface.php that the external application should require:
$loader = require __DIR__.'/autoload.php';
require_once (__DIR__.'/APIAuth.php');
return new APIAuth();
and an example of a working APIAuth I wrote (the code is kind of messy: remember this is just a try but you can get the idea):
class APIAuth
{
public function __construct()
{
//dev_local is a personal configuration I'm using.
$kernel = new AppKernel('dev_local', false);
$kernel->loadClassCache();
$kernel->boot();
$doctrine = $kernel->getContainer()->get('doctrine');
$em = $doctrine->getManager();
$users = $em->getRepository('BelkaTestBundle:User')->findUsersStartingWith('thisisatry');
}
by calling it by the shell everything works and I'm happy with it:
php app/authInterface.php
but I'm wondering if I'm doing in the best way possible in terms of:
resources am I loading just the resources I really need to run my code? Do I really need the kernel? That way everything is properly loaded - including the DB connection- but I'm not that sure if there are other ways to do it lighter
symfony logics am I interacting with symfony the right way? Are there better ways?
Symfony allows using its features from the command line. If you use a CronJob or another application, and want to call your Symfony application, you have two general options:
Generating HTTP endpoints in your Symfony application
Generating a command which executes code in your Symfony application
Both options will be discussed below.
HTTP endpoint (REST API)
Create a route in your routing configuration to route a HTTP request to a Controller/Action.
# app/config/routing.yml
test_api:
path: /test/api/v1/{api_key}
defaults: { _controller: AppBundle:Api:test }
which will call the ApiController::testAction method.
Then, implement the testAction with your code you want to excecute:
use Symfony\Component\HttpFoundation\Response;
public function testAction() {
return new Response('Successful!');
}
Command
Create a command line command which does something in your application. This can be used to execute code which can use any Symfony service you have defined in your (web)application.
It might look like:
// src/AppBundle/Command/TestCommand.php
namespace AppBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class GreetCommand extends ContainerAwareCommand
{
protected function configure()
{
$this
->setName('myapp:section:thecommand')
->setDescription('Test command')
->addArgument(
'optionname',
InputArgument::OPTIONAL,
'Test option'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$option = $input->getArgument('optionname');
if ($option) {
$text = 'Test '.$option;
} else {
$text = 'Test!';
}
$output->writeln($text);
}
}
Look here for documentation.
Call your command using something like
bin/console myapp:section:thecommand --optionname optionvalue
(Use app/console for pre-3.0 Symfony installations.)
Use whichever option you think is best.
One word of advice. Do not try to use parts of the Symfony framework when your application is using the full Symfony framework. Most likely you will walk into trouble along the way and you're making your own life hard.
Use the beautiful tools you have at your disposal when you are already using Symfony to build your application.