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;
Related
I'm working on small PHP framework (for learning). It has a file with LoginController class containing a method with Route attribute (code below). Is there any way to get the name of the method in the Route attribute class using Reflection?
Class with method using attribute:
class LoginController {
#[Route('GET', '/login')]
public function index() {
// some code
}
}
"Route" attribute class:
use Attribute;
use ReflectionMethod;
#[Attribute]
class Route {
public function __construct($method, $routeUri) {
// Can I get the method name ("index") from attribute instead of writing it?
// And the class name?
$reflection = new ReflectionMethod(\App\Controllers\LoginController::class, 'index');
$closure = $reflection->getClosure();
// Register a route...
Router::add($method, $routeUri, $closure);
}
}
Reflection is an option, but please be aware that you will be looping over all the attributes of all the methods in a class (at least until a matching one is found). Of course, if all routes need to be registered, this isn't that bad.
$classRef = new ReflectionClass(LoginController::class);
foreach ($classRef->getMethods() as $method) {
$methodRef = new ReflectionMethod($method->class, $method->name);
foreach ($methodRef->getAttributes() as $attribute) {
if (
$attribute->getName() === 'Route'
&& $attribute->getArguments() === [$method, $routeUri]
) {
// you can register your route here
}
}
}
As far as classes go, the easiest way to go is just make an array with all your controller class names. There are packages out there that can detect all classes in a given namespace, which could be used for autodetecting your controllers.
I am trying to create a global helper to store site settings in my Laravel application. Meaning, I need a helper to set settings from a controller and access that settings object from anywhere using the same helper. I don't need to store these settings on a database or in files. That's why I need these kinds of functionality.
What is did are as follows,
Create a class in app/Helpers directory called SettingsHelper and auto load the directory.
namespace App\Helpers;
class SettingsHelper{
protected $vars;
public function all(){
return $this->vars;
}
public function get($key, $default = null){
if (is_array($this->vars) && array_key_exists($key, $this->vars)) {
return $this->vars[$key];
}
return $default;
}
public function put($key, $value){
return $this->vars[$key] =$value;
}
}
Create a helper function to resolve the class if not already resolved
if ( ! function_exists('settings')) {
function settings(){
if (app('\App\Helpers\SettingsHelper')) {
return app('\App\Helpers\SettingsHelper');
}
return app()->make('\App\Helpers\SettingsHelper');
}
}
Set and get setting using the helper
settings()->put('test', 'test2');
dd(settings()->all());
But null is returned. I tried dumping settings()->put('test', 'test2') and it returned the passed value test2.
Is this the correct approach for this?
You should register your class as a singleton in the application container before you can use it. Otherwise, the app() function will create a new instance on every use. You can also give the instance a name in the container, this way, you don't have to create your own global function.
In your AppServiceProvider.php:
$this->app->singleton('settings', function ($app) {
return new \App\Helpers\SettingsHelper;
});
And now you can use your class with:
app('settings')->put('test', 'test2');
dd(app('settings')->all());
Create a service provider php artisan make:provider HelperServiceProvider
Then in the register method do something like:
public function register()
{
foreach (glob(app_path().'/Helpers/*.php') as $filename) {
require_once($filename);
}
}
Make a singleton in a provider, example AppServiceProvider.php.
use App\Helpers\SettingsHelper;
$this->app->singleton(SettingsHelper::class, function ($app) {
return new SettingsHelper($vars); // create your class with the vars you need
});
Everywhere in your app there can only be 1 settings helper and you can get it like so.
use App\Helpers\SettingsHelper;
app(SettingsHelper::class);
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 separate validate_date functions in controller1.php, controller2.php, and controller3.php. I want to create a global function in DateTimeHelper.php to validate dates instead of creating a function in each controller for this functionality. I am using Laravel in the backend
Controller1:
private function validate_date($date)
{
$split_date = explode('/', $date);
if (count($split_date) == 3) {
return checkdate($split_date [0], $split_date [1], $split_date [2]);
}
return false;
}
Controller2 && controller 3:
private function validate_date($date)
{
if ($this->check_field($date) && $date !== 'Invalid date') {
return true;
}
return false;
}
My problem is that I am using different functions in different controllers, I need a global function to validate dates instead of creating a function in each controller for this functionality.
The following method will probably do what you're looking for:
Create a new file called DateTimeHelper.php inside a (new) Helpers folder. The final path will be app\Helpers\DateTimeHelper.php.
The content of DateTimeHelper.php will look like:
<?php
namespace App\Helpers;
class DateTimeHelper {
public function validate()
{
}
public function checkField()
{
}
}
Now edit the controllers where you want to use the class, add a procted property $dateTimeHelper and use Laravel's Automatic Injection like this:
<?php
namespace App\Http\Controllers;
use App\Helpers\DateTimeHelper;
class UserController extends Controller
{
protected $dateTimeHelper;
/**
* Create a new controller instance.
*
* #param DateTimeHelper $dateTimeHelper
* #return void
*/
public function __construct(DateTimeHelper $dateTimeHelper)
{
$this->dateTimeHelper = $dateTimeHelper;
}
public function show($date)
{
return $this->dateTimeHelper->validate($date);
}
}
Hopefully this should solve the problem, however, I'd recommend to look into Laravel validation.
How about a Service Class instead of helper function and put all possible date validation from the system into this Service Class?
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 !!