I work with Redis loaded as a service to inject followers to a entity. So i've a entity like User that has a method like getFollowers. I don't want to mix service with entities, so I make a listener that subscribe to postLoad events in Doctrine.
The question is how call the service only when I call the getFollowers method.
My code...
EventListener:
public function postLoad(LifecycleEventArgs $eventArgs)
{
$redisService = get the service loaded with DIC in constructor.
if ($eventArgs->getEntity() instanceof User) {
$user = $eventArgs->getEntity();
$user->setFollowers($redisService->getFollowers($user));
}
}
User entity:
public function setFollowers(Array $followers) {
$this->followers = $followers
}
My problem is that on every load of class user, the RedisService is called and loaded, and I'd like to call the service ONLY on $user->getFollowers
Finally I get the answer...
In my listener postLoad I assign a closure to property of object:
$socialGraph = $this->socialGraph;
$getFollowers = function() use ($socialGraph, $user) {
return $socialGraph->getFollowers($user->getId());
};
$user->setFans($getFollowers);
Now, in my object it's possible to call a method into a property with:
public function getFans()
{
return call_user_func($this->fans);
// another way
return $this->fans->__invoke();
}
Wrap it as singleton. Something like that?
if (is_callable($this->_lazyLoad)) {
$this->_lazyLoad = $this->_lazyLoad($this);
}
return $this->_lazyLoad;
Related
I am using laravel framework to develop API's, i am using laravel-saptie audit package to monitor all the users activity.now i have to modify or add the some functionality in LogsActivity Trait boot method ,after some research i am using like following
LogsActivity.php
trait LogsActivity{
protected static function bootLogsActivity(): void
{
// Hook into eloquent events that only specified in $eventToBeRecorded array,
// checking for "updated" event hook explicitly to temporary hold original
// attributes on the model as we'll need them later to compare against.
static::eventsToBeRecorded()->each(function ($eventName) {
if ($eventName === 'updated') {
static::updating(function (Model $model) {
$oldValues = (new static())->setRawAttributes($model->getRawOriginal());
$model->oldAttributes = static::logChanges($oldValues);
});
}
static::$eventName(function (Model $model) use ($eventName) {
$model->activitylogOptions = $model->getActivitylogOptions();
if (! $model->shouldLogEvent($eventName)) {
return;
}
$changes = $model->attributeValuesToBeLogged($eventName);
$description = $model->getDescriptionForEvent($eventName);
$logName = $model->getLogNameToUse();
// Submitting empty description will cause place holder replacer to fail.
if ($description == '') {
return;
}
if ($model->isLogEmpty($changes) && ! $model->activitylogOptions->submitEmptyLogs) {
return;
}
// User can define a custom pipelines to mutate, add or remove from changes
// each pipe receives the event carrier bag with changes and the model in
// question every pipe should manipulate new and old attributes.
$event = app(Pipeline::class)
->send(new EventLogBag($eventName, $model, $changes, $model->activitylogOptions))
->through(static::$changesPipes)
->thenReturn();
// Actual logging
$logger = app(ActivityLogger::class)
->useLog($logName)
->event($eventName)
->performedOn($model)
->withProperties($event->changes);
if (method_exists($model, 'tapActivity')) {
$logger->tap([$model, 'tapActivity'], $eventName);
}
$logger->log($description);
// Reset log options so the model can be serialized.
$model->activitylogOptions = null;
});
});
}
}
<?php
namespace App\Http\Traits;
use ReflectionMethod;
use Spatie\Activitylog\Models\Activity;
use Spatie\Activitylog\Traits\LogsActivity;
trait CustomLogTrait{
use LogsActivity
{
LogsActivity::bootLogsActivity as parentbootLogsActivity;
}
protected static $logOnlyDirty = true;
public static function bootLogsActivity(){
$this->parentbootLogsActivity;
static::creating(function(Activity $model){
$act = $model->all()->last();
$act;
});
}
}
i am facing this problem Using $this when not in object context {"exception":"[object] (Error(code: 0): Using $this when not in object context.instead of resolving this one if i use directly in customTrait inside bootLogsActivity() function like this
LogsActivity::bootLogsActivity
still this one also throwing an error like protected one can't able to access.can anyone help to me override the LogsActivity boot method inside my customLogTrait ?
Thanks in Advance !
You are trying to access this from a static context.
Thus, the line:
$this->parentbootLogsActivity;
Shall be modified to:
self::parentbootLogsActivity;
I am currently implementing repository pattern and dependency injection on my laravel project. But when I inject two or more class, I got this error
Too few arguments to function App\Repositories\UserRepository::__construct(), 0 passed
I don't know what's wrong but I think I did it correctly. Here's my code:
My service provider:
public function register()
{
$this->app->bind(LoanRepository::class, function() {
return new LoanRepository(new Loan, new UserRepository);
});
}
and here's my Repository
public function __construct($loan, $userRepository)
{
$this->loan = $loan;
$this->userRepository = $userRepository;
}
does anyone experience this?
The error is telling you that the UserRepository class is expecting some arguments that you are not passing it. Below would be an example of how you would pass a constructor argument to your new UserRepositiry call.
public function register()
{
$this->app->bind(LoanRepository::class, function() {
return new LoanRepository(new Loan, new UserRepository($constructorArgsHere);
});
}
I haven't worked with Laravel so I don't know what the UserRepository class constructor is looking for as arguments, but that should be an easy thing to find out just looking at the file where that class is defined or a quick google search.
I have a manually registering event and related listener. For this i want to add test so i checked laravel Mocking Test in documentation but i didn't find any way to test manually registering event with parameter listener. So anyone help me how to do this? Below i attached working related code.
Event is calling in the TeamObserver deleting method like below
class TeamObserver
{
public function deleting(Team $team)
{
event('event.team.deleting', array('team' => $team));
}
}
Event and Listeners are registered in EventServiceProvider boot method like below
public function boot()
{
parent::boot();
Event::listen(
'event.team.deleting',
'Listeners\TeamDeletingListener'
);
}
TeamDeletingListener is look like below
class TeamDeletingListener
{
public function handle($team)
{
\Log::info('Deleting Inventory Module');
\Log::info($team);
}
}
The simplest way to do this, is to replace the real implementation of your listener with a mocked one. Here's an example test. It will fail if you remove event('event.team.deleting', array('team' => $team)); or pass a different team. If I got you right, that's what you want to achieve.
public function testTeamDeletion()
{
// Persist team that should be deleted
$team = new Team();
$team->name = 'My Team';
$team->save();
// Mock the listener
$this->mock(
TeamDeletingListener::class,
function (MockInterface $mock) use ($team) {
// Expectation
$mock->shouldReceive('handle')
->with($team)
->once();
}
);
// Delete team
$team->delete();
}
I have a selection control on a blade form that is to be refreshed via ajax through this function:
function getOpciones(tbName) {
$.get('/ajax/read-data/' + tbName, function(data){
return (data);
});
}
The function takes a string variable 'tbName' whith the name of the table the control is related to, and passes it on as a parameter to the route:
Route::get('/ajax/read-data/{modelo}', 'AjaxController#readData');
Then the controller should get the parameter {modelo}, and retrieve the records in that table:
use App\RegFiscal;
public function readData($modelo) {
$arreglo = $modelo::all();
return response($arreglo);
}
But even though I am referencing the model with 'use App\RegFiscal', all I get is this error in laravel log:
2018-03-23 18:52:08] local.ERROR: exception
'Symfony\Component\Debug\Exception\FatalErrorException' with message
'Class 'RegFiscal' not found' in
C:\wamp64\www\laravel\cte\app\Http\Controllers\AjaxController.php:32
I´m new to Laravel, so needless to say I am lost and any help would be greatly appreciated. Thanks!
Just because you use App\RegFiscal doesn't mean $modelo is associated with it.
What you can do, though, is use app("App\\$modelo") to load in your model based on the parameter you get from the router. You would no longer need to use App\RegFiscal either.
$arreglo = app("App\\$modelo");
return response($arreglo::all());
This is assuming your model is stored in the default app directory within your Laravel project. If not you can change "App\" to where ever it is stored. If for example your model is in app\models\modelname.php it would be "App\Models\\$modelo".
You can do this as the following:
public function readData($modelo) {
$modelName = '\App' . '\\' . $modelo;
$class = new $modelName();
arreglo = $class::all();
return response($arreglo);
}
To those like me who wanted to inject it on a constructor, here's how to do it:
~$ php artisan make:provider MyProvider
Then override the register function like so:
class MyProvider implements ServiceProvider {
/** #override */
public function register() {
$this->app->bind(ShapeInterface::class, function ($app) {
return new Square($app->make(MyModel::class));
});
}
}
The ShapeInterface is a simple interface and Square is a simple class that implements the shape interface with a constructor parameter of the eloquent model.
class Square implements ShapeInterface {
private MyModel $model;
function __construct(MyModel $model) {
$this->model = $model;
}
...
}
I have implemented following code to run a code on before any action of any controller. However, the beforeFilter() function not redirecting to the route I have specified. Instead it takes the user to the location where the user clicked.
//My Listener
namespace Edu\AccountBundle\EventListener;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
class BeforeControllerListener
{
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller))
{
//not a controller do nothing
return;
}
$controllerObject = $controller[0];
if (is_object($controllerObject) && method_exists($controllerObject, "beforeFilter"))
//Set a predefined function to execute Before any controller Executes its any method
{
$controllerObject->beforeFilter();
}
}
}
//I have registered it already
//My Controller
class LedgerController extends Controller
{
public function beforeFilter()
{
$commonFunction = new CommonFunctions();
$dm = $this->getDocumentManager();
if ($commonFunction->checkFinancialYear($dm) == 0 ) {
$this->get('session')->getFlashBag()->add('error', 'Sorry');
return $this->redirect($this->generateUrl('financialyear'));//Here it is not redirecting
}
}
}
public function indexAction() {}
Please help, What is missing in it.
Thanks Advance
I would suggest you follow the Symfony suggestions for setting up before and after filters, where you perform your functionality within the filter itself, rather than trying to create a beforeFilter() function in your controller that is executed. It will allow you to achieve what you want - the function being called before every controller action - as well as not having to muddy up your controller(s) with additional code. In your case, you would also want to inject the Symfony session to the filter:
# app/config/services.yml
services:
app.before_controller_listener:
class: AppBundle\EventListener\BeforeControllerListener
arguments: ['#session', '#router', '#doctrine_mongodb.odm.document_manager']
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
Then you'll create your before listener, which will need the Symony session and routing services, as well as the MongoDB document manager (making that assumption based on your profile).
// src/AppBundle/EventListener/BeforeControllerListener.php
namespace AppBundle\EventListener;
use Doctrine\ODM\MongoDB\DocumentManager;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use AppBundle\Controller\LedgerController;
use AppBundle\Path\To\Your\CommonFunctions;
class BeforeControllerListener
{
private $session;
private $router;
private $documentManager;
private $commonFunctions;
public function __construct(Session $session, Router $router, DocumentManager $dm)
{
$this->session = $session;
$this->router = $router;
$this->dm = $dm;
$this->commonFunctions = new CommonFunctions();
}
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
if ($controller[0] instanceof LedgerController) {
if ($this->commonFunctions->checkFinancialYear($this->dm) !== 0 ) {
return;
}
$this->session->getFlashBag()->add('error', 'Sorry');
$redirectUrl= $this->router->generate('financialyear');
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
}
}
If you are in fact using the Symfony CMF then the Router might actually be ChainRouter and your use statement for the router would change to use Symfony\Cmf\Component\Routing\ChainRouter;
There are a few additional things here you might want to reconsider - for instance, if the CommonFunctions class needs DocumentManager, you might just want to make your CommonFunctions class a service that injects the DocumentManager automatically. Then in this service you would only have to inject your common functions service instead of the document manager.
Either way what is happening here is that we are checking that we are in the LedgerController, then checking whether or not we want to redirect, and if so we overwrite the entire Controller via a callback. This sets the redirect response to your route and performs the redirect.
If you want this check on every single controller you could simply eliminate the check for LedgerController.
.
$this->redirect() controller function simply creates an instance of RedirectResponse. As with any other response, it needs to be either returned from a controller, or set on an event. Your method is not a controller, therefore you have to set the response on the event.
However, you cannot really set a response on the FilterControllerEvent as it is meant to either update the controller, or change it completely (setController). You can do it with other events, like the kernel.request. However, you won't have access to the controller there.
You might try set a callback with setController which would call your beforeFilter(). However, you wouldn't have access to controller arguments, so you won't really be able to call the original controller if beforeFilter didn't return a response.
Finally you might try to throw an exception and handle it with an exception listener.
I don't see why making things this complex if you can simply call your method in the controller:
public function myAction()
{
if ($response = $this->beforeFilter()) {
return $response;
}
// ....
}
public function onKernelController(FilterControllerEvent $event)
{
$request = $event->getRequest();
$response = new Response();
// Matched route
$_route = $request->attributes->get('_route');
// Matched controller
$_controller = $request->attributes->get('_controller');
$params = array(); //Your params
$route = $event->getRequest()->get('_route');
$redirectUrl = $url = $this->container->get('router')->generate($route,$params);
$event->setController(function() use ($redirectUrl) {
return new RedirectResponse($redirectUrl);
});
}
Cheers !!