Writing (Mono)logs to different database tables - symfony

I'm currently logging all interactions to my database (added user, updated user, ...) in one table.
An example from my userService:
public function addUserAction()
{
$this->logger->alert("Added user!");
// Code that adds the user to the database (not relevant for my question)
// ...
}
public function updateUserAction($user)
{
$this->logger->alert("Updated user!");
// Code that updates the user to the database (not relevant for my question)
// ...
}
Now I'd like to have separate tables as followed: log_added_users, log_updated_users, etc ...
I have one channel and one handler configured in my config.yml
I have injected the logger into the userService and tagged it with my channel.
services:
userService:
class: Acme\Services\UserService
arguments: ["#logger"]
tags:
- { name: monolog.logger, channel: my_channel }
I'm wondering where I should define in which table to add a certain log entry? I don't think I need more channels as I can only define one for the injected logger (as seen in the config snippet above)
So I'm thinking to add more handlers (one per db table). The thing is: when the log is being written, it will go through all handlers and thus add the same log to all tables?
Or should I just put my updateUserAction in another service, so I will be able to work with another channel?
Any help is appreciated :)

I found out that, for every channel you create, a service gets created automatically for you.
So I just injected another logger into the userService and tagged them with the new channel.
That way I can say for example:
public function addUserAction()
{
$this->logger->alert("user added");
}
public function updateUserAction($user)
{
$this->updateLogger->alert("user updated");
}

Related

Api-Platform: using PUT for creating resources

I would like to use the PUT method for creating resources. They are identified by an UUID, and since it is possible to create UUIDs on the client side, I would like to enable the following behaviour:
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource exists, update it
on PUT /api/myresource/4dc6efae-1edd-4f46-b2fe-f00c968fd881 if this resource does not exist, create it
It's possible to achieve this by implementing an ItemDataProviderInterface / RestrictedDataProviderInterface.
However, my resource is actually a subresource, so let's say I want to create a new Book which references an existing Author.
My constructor looks like this:
/**
* Book constructor
*/
public function __construct(Author $author, string $uuid) {
$this->author = $author;
$this->id = $uuid;
}
But I don't know how to access the Author entity (provided in the request body) from my BookItemProvider.
Any ideas?
In API Platform many things that should occur on item creation is based on the kind of request it is. It would be complicated to change.
Here are 2 possibilities to make what you want.
First, you may consider to do a custom route and use your own logic. If you do it you will probably be happy to know that using the option _api_resource_class on your custom route will enable some listeners of APIPlaform and avoid you some work.
The second solution, if you need global behavior for example, is to override API Platform. Your main problem for this is the ReadListener of ApiPlatform that will throw an exception if it can't found your resource. This code may not work but here is the idea of how to override this behavior:
class CustomReadListener
{
private $decoratedListener;
public function __construct($decoratedListener)
{
$this->decoratedListener = $decoratedListener;
}
public function onKernelRequest(GetResponseEvent $event)
{
try {
$this->decoratedListener->onKernelRequest($event);
} catch (NotFoundHttpException $e) {
// Don't forget to throw the exception if the http method isn't PUT
// else you're gonna break the 404 errors
$request = $event->getRequest();
if (Request::METHOD_PUT !== $request->getMethod()) {
throw $e;
}
// 2 solutions here:
// 1st is doing nothing except add the id inside request data
// so the deserializer listener will be able to build your object
// 2nd is to build the object, here is a possible implementation
// The resource class is stored in this property
$resourceClass = $request->attributes->get('_api_resource_class');
// You may want to use a factory? Do your magic.
$request->attributes->set('data', new $resourceClass());
}
}
}
And you need to specify a configuration to declare your class as service decorator:
services:
CustomReadListener:
decorate: api_platform.listener.request.read
arguments:
- "#CustomReadListener.inner"
Hope it helps. :)
More information:
Information about event dispatcher and kernel events: http://symfony.com/doc/current/components/event_dispatcher.html
ApiPlatform custom operation: https://api-platform.com/docs/core/operations#creating-custom-operations-and-controllers
Symfony service decoration: https://symfony.com/doc/current/service_container/service_decoration.html

Symfony / Doctrine - Access service inside an entity

I've create an Encryption service and want to access it inside an entity.
Stn like this:
public function setCompanyName(string $companyName, Encryption $encryption)
{
$this->companyName = $encryption->encrypt($companyName);
}
But is it possible to do it without calling this function setCompanyName with two parameters? Do I have to inject container and call service inside the function?
You want to store encrypted data in db right ?
Best way to do this is to create event listener that 'll fire up on each entity save , and make encryption there (you can inject anything you want to listener)
and second event listener that fire up when loading data from db to make decryption
If you do this right all encrytion/decryption thing will be transparent in code (it will only exists in those listeners )
look at this
https://symfony.com/doc/3.4/doctrine/event_listeners_subscribers.html
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html
It is not possible to inject a service inside an entity.
But you could use an entity listener to use your service and encrypt the company name juste before persisting.
Take a look at https://symfony.com/doc/current/bundles/DoctrineBundle/entity-listeners.html
Maybe create a static method inside Encrypt class, and call it directly in entity?
class Encryption {
public static function encrypt(string $string) {
// your code
}
}
public function setCompanyName(string $companyName) {
$this->companyName = Encryption::encrypt($companyName);
}

Action requires multiple controllers to execute

I have a UserController that has a Destroy function. It is a rather complex function because it demands to destroy all user's data. I have another action, from the Admin panel that deletes all data from a specific set of users.
Since I don't want to replicate the code from the UserController, I would like to call the Destroy function from UserController for each User to destroy its data.
How should I proceed?
Thanks in advance.
Why not move this functionality to a common class method which can be accessed from both the controllers as needed ?
public class UserManager
{
public void Destroy(List<int> userIdsToDestroy)
{
foreach(var userId in userIdsToDestroy)
{
//Execute code to destroy
}
}
}
and from your action methods, you can call it like
var mgr = new UserManager();
var badUsers = new List<int> { 1,2,3};
mgr.Destroy(badUsers);
Update the badUsers variable value as needed based on from where you are calling it.
Shared functionality like this would ideally be in a business layer, and both controllers would call that code. If it's a little app, you could just create a separate folder structure for shared code. Larger projects would have a business layer dll.
Why not make the Destroy() method as a Non-Action method then like
[Non-Action]
public void Destroy(User user)
{
// code goes here
}
You can as well make this Destroy() function as part of your business layer logic instead of handling this in controller. In that case, you call it from anywhere.
If you want it to be #controller, you can as well consider usig [ChildActionOnly] action filter attribute.

Is it possible to create Attribute for controller, which executes all query into db via one transaction?

I mean next situation. This example a little bit ugly, but it shows what exactly I want.
[MyAttribute]
public MyController
{
[Post]
public void CoolStuff(int id, Item item)
{
User user = _userRepository.Get(id);
Bucket bucket = _bucketRepository.Get(user.bucketId);
bucket.Add(item);
_bucketRepository.Save(bucket);
}
}
There are 3 queres into db. I want that [MyAttribute] collects these queries in transaction and executes. Do you have any ideas?
Take a look at the ActionFilterAttribute. You can derive your custom attribute from ActionFilterAttribute and override two methods:
OnActionExecuting - to open a transaction
OnActionExecuted - to comit or rollback the transaction
Hope it helps!

Variable bound to whole application through requests

I want a variable bound to the application scope, (in java that would be application scope).
I thought service should be the thing to use in order to reach my goal.
I created a service test
<?php
namespace Acme\MyBundle\Service;
class test {
public $count;
public function __construct() {
$this->count = 0;
}
public function addCount() {
$this->count++;
}
}
which I declared in services.yml
acme.my.service.test:
class: Acme\MyBundle\Service\test
I call it in my controller
public function testAction() {
$this->get('acme.my.service.test')->addCount();
return $this->render('AcmeMyBundle:Test:test.html.twig');
}
But when I retrieve it in my twig, the value is 1, no matter how much I refresh or go with multiple session on the url bound to my testAction.
=> It means that constructor is called each time.
So is that the right way to do? I thought services were created once and reused then, but I may be mistaken.
Could you please enlighten me?
Thank you,
copndz
I found what I was looking for, APC caching system.
Easy to use and integrated to doctrine common.
How to cache in Symfony 2?
http://docs.doctrine-project.org/en/latest/reference/caching.html

Resources