How to use multiple endpoints for Symfony NexySlackBundle - php

I am using the nexylan/slack Bundle for my symfony 3.4 application. I configured the slack Incoming WebHook for #general channel and it's working as expected. The bundle configuration looks something like:
nexy_slack:
# If you want to use an another httplug client service.
http:
client: httplug.client
# The Slack API Incoming WebHooks URL.
endpoint: https://hooks.slack.com/services/ABCD/987ABC
channel: null
username: null
icon: null
link_names: false
unfurl_links: false
unfurl_media: true
allow_markdown: true
markdown_in_attachments: []
Now I have another channel called #dev and I've added the Incoming WebHook and received the endpoint. I also want to send messages to the dev channel.
My question is, how can I configure the dev channel endpoint too in order to use it. Is there any way I can do this?
Here is the Slack Bundle

It looks like the bundle only supports 1 endpoint. If you want to have multiple endpoints you either have to fork or send in a PR.
Basically what you need to do, is adjust both files in src/DependencyInjection.
In Configuration.php you need to ensure that you can define multiple endpoints by adding a parent array node, e.g. called endpoints. Then inside NexySlackExtension you can foreach through each endpoint configuration and do the same configuration as before just add a prefix or suffix. So something like:
$configuration = new Configuration();
$endpointConfigs = $this->processConfiguration($configuration, $configs);
foreach ($endpointConfigs['endpoints'] as $config) {
// ....
}
You might also want do add some special handling for a "default" endpoint. This should already do the trick, although it might need some adjustments as I haven't looked into the Bundle in detail. Maybe you can also contact the author via a ticket in the Issue tracker and they can help you write a PR.

Related

Laravel remembers original response during http tests

Given the following pest test:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The above should fully work; however, the second request post('/courses')...
fails saying that:
Failed asserting that <...> contains "WebTechnologies".
If I remove the first request:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The test passes.
If I remove the second request instead:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
});
It also passes.
So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.
I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...
https://github.com/laravel/sanctum/issues/377
One of the maintainers of Laravel Said:
You can't perform two HTTP requests in the same test method. That's not supported.
I would have wanted a much clearer explanation on why it's not supported.
but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)
UPDATE:
My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...
but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.
here is the file that creates the request when doing a test...
vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
it's on the call method line: 526 (Laravel v9.26.1)
as you can see...
Laravel only uses 1 app instance... not rebuilding the app...
Line 528: $kernel = $this->app->make(HttpKernel::class);
https://laravel.com/docs/9.x/container#the-make-method
the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
My guess here is that the HttpKernel::class is a singleton.
P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.
TL;DR.
You can't perform two HTTP requests in the same test method. That's not supported.
UPDATE:
I was not able to stop myself...
I found Laravel initializing Kernel as a singleton
/{probject_dir}/bootstrap/app.php:29-32
Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades.
https://laravel.com/docs/9.x/container#binding-a-singleton
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests

How do I configure base_uri for my http request in symfony?

I am using symfony's HttpClientInterface to make a request to an API.
I know that my base uri will never change and I would like to configure it in /config/packages/framework.yml
Yet whenever I try to set the base_uri, symfony tells me that it is not an available option for http_client.
So, I have reasoned that it most likely must be an option of "scoped_clients" yet whenever I try the code below, Symfony tells me I am missing a base_uri and host or scheme.
http_client:
max_host_connections: 10
scoped_clients:
base_uri: '%app.api.coolapp.base_uri%'
default_options:
headers: { 'X-Powered-By': 'my App' }
max_redirects: 7
So, I think I am missing the concept of the scoped client, because obviously it needs more information or something to get my request url correct. What am I missing?
Thats correct behaviour. Symfony needs a named client for that:
http_client
scoped_clients:
your_api.client: # <-- your configuration's missing this line
base_uri: '%app.api.coolapp.base_uri%'
# ...

Getting the domain dynamically in a Symfony Console Command

I am encountering precisely this problem:
Symfony Docs - How to Generate URLs and Send Emails from the Console
Our email templates are being filled with "localhost" instead of "my.real-domain.name". When constructing links to the application using twig's "url('some/path')".
However, where Symfony is usually "one installation per domain", our application is designed so a single instance can handle multiple domains. It constructs the necessary configuration through various configuration channels, with each customer being one channel.
Thus I would like to avoid configuring "router.request_context.host" and others for every single customer channel.
So I would like to grab the domain to be used from a "--domain" console parameter that we give to every console command instad.
But instead of doing it in every single command, I would need to do this in one central location that grabs the domain and configures "router.request_context" dynamically according to the console parameter.
Is there any way I can do that?
You can register a listener on the event dispatcher which listens to the ConsoleEvents::COMMAND and configure it further before the run method on the command is called.
For example, you can use a setter or change the input on the ConsoleCommandEvent instance.
https://symfony.com/doc/3.4/components/console/events.html
I'm not sure this is an ideal solution, but this is what I could think of at the moment.
You can tell to the router which domain use when generating an absolute URL, as example:
$domain = $input->getArgument('domain');
$context = $this->getContainer()->get('router')->getContext();
$context->setHost($domain);
$route = $this->getContainer()->get('router')->generate('welcome_page', $params, UrlGeneratorInterface::ABSOLUTE_URL);
$output->writeln('Use the following url to show the welcome page for the provided domain');
$output->writeln(sprintf('<info>%s</info>', $route));
Hope this help

How to fake the HTTP_USER_AGENT in a Symfony2 Listener UnitTest?

I have a Listener, which behaves differently depending on the HTTP_USER_AGENT:
if ($request->server->get('HTTP_USER_AGENT') == $this->zabbixUserAgent) {
VisitorHolder::set($visitor);
} else {
VisitorHolder::set($this->visitorService->persist($visitor));
}
I want to avoid saving all Zabbix requests to our database. That works fine, but how can I fake the user agent in my unit test, so that my tests cover this case?
Creating a new Request and setting the user agent there is thoroughly ignored:
$this->currentRequest = new Request(
[], // GET parameters
[], // POST parameters
[], // request attributes (parameters parsed from the PATH_INFO, ...)
[], // COOKIE parameters
[], // FILES parameters
['HTTP_USER_AGENT' => 'zbx'], // SERVER parameters
null // raw body data
);
$this->requestStack
->expects($this->any())
->method('getCurrentRequest')
->willReturn($this->currentRequest);
A var_dump in the unit test tells me, that my user agent is still null and my case is not covered.
Any idea how I can set the user agent for this case?
If you extracted the actual check to a function elsewhere in the class, you can then mock or otherwise override that check within the class and keep it as a unit-test that does not need to fake a HTTP request at all.
For full integration tests, If you extracted the actual check to a separate service, then you can override the check with a difference configuration in a config_test.yml file, and using a different copy of the service that will always report false in a test-environment.
# config_test.yml file:
app_zabbix_detect.detector:
class: AppBundle\Services\ZabbixDetectorAlwaysFalse
In the main file it would be
# config.yml file: (or services.yml)
app_zabbix_detect.detector:
class: AppBundle\Services\ZabbixDetector # real test

Log contextual data (without specifying it explicitly)

I'm working on a multi-tenant app where I need to log a lot more data than what I pass to the log facade. What I mean is, every time I do this...
Log::info('something happened');
I get this:
[2017-02-15 18:12:55] local.INFO: something happened
But I want to get this:
[2017-02-15 18:12:55] [my ec2 instance id] [client_id] local.INFO: something happened
As you can see I'm logging the EC2 instance ID and my app's client ID. I'm of course simplifying this as I need to log a lot more stuff in there. When I consume and aggregate these logs, having these extra fields make them incredibly handy to figure what things went wrong and where.
In the Zend Framework, I usually subclass the logger and add these extra fields in my subclass but I'm not sure how I can do that with Laravel. I can't find where the logger is instantiated so that I can plug my custom logger in (if that is even the way to go in Laravel).
So, I'm not asking how to get the EC2 instance ID and the other stuff, I'm only asking what the proper way to "hot wire" the Laravel logger is to be able to plug this in.
Just an idea... the logger in Laravel is really a Monolog instance... You could push a handler on it and do whatever processing you want for each entry... like so...
<?php
$logger->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'Hello world!';
return $record;
});
As per the Laravel doc you can hook up into the monolog config at boot...
Custom Monolog Configuration
If you would like to have complete control over how Monolog is
configured for your application, you may use the application's
configureMonologUsing method. You should place a call to this method
in your bootstrap/app.php file right before the $app variable is
returned by the file:
$app->configureMonologUsing(function ($monolog) {
$monolog->pushHandler(...);
});
return $app;
So instead just push a processor on the $monolog instance passed to the hook...
Just an idea, I have not tried this in Laravel but used Monolog before...

Categories