How to create user activity logs? - php

My goal is to keep track of activity logs when any users update their username or email.
I have users table in my database lay out like this :
id
username
email
I decided to created a logs table that will contain :
username
username_new
email
email_new
created_at
user_id
After doing a bunch of researchs, some people suggested me to use Event::fire and Event::listen.
So … I been looking at this again and again for awhile now.
I got this.
For each action that a user takes should fire an event.
$event = Event::fire(‘user.log’, array($user));
Event::listen(‘user.log’, function($user)
{
$log->username = $user->username;
$log->username_new = Input::get(‘username’);
$log->email = $user->email;
$log->email_new = Input::get(‘email’);
$log->created_at = Input::get('created_at');
$log->user_id = $user->id;
$log->save();
});
I know that I put these code in
app/start/global.php OR app/events.php.
Well, I'm NEW to Laravel. But I believed that, "Every artist was once an amateur" - right ?
I'm not sure if my approach will work. BUT I hope that someone can point out if I did anything completely wrong, or possibly forgot anything.
Is what I am doing is logically make sense ?
HUGE thanks to everyone that contribute on this question !

Not a real answer to your question (which you seem to have answered yourself), but: you might want to consider a more generic solution. For example, lets say you have an object User like this
User {
protected $email;
protected $username;
protected $name;
}
When the user triggers a save, you trigger an event user.change($oldUser, $newUser) which, as param names suggest, propagate the user state before and after the change. You handle it with something like:
Event::listen('user.change', function($oldUser, $newUser) {
// you could do a recursive diff here
$diff = array_diff((array) $newUser, (array) $oldUser);
// diff here contains only the changes made in that request
// if you add more properties to user, they will work too
// as there might be one or more properties in the diff, a non-prescribed columns
// storage such as MongoDB might be a best choice
}
Food for thought in any way, HTH.

Related

DDD - how to deal with get-or-create logic in Application Layer?

I have an DailyReport Entity in my Domain Layer. There are some fields in this object:
reportId
userId
date
tasks - Collection of things that user did in given day;
mood - how does the user felt during the whole day;
Also, there are some methods in my Application Service:
DailyReportService::addTaskToDailyReport
DailyReportService::setUserMoodInDailyReport
The thing is that both of these methods require DailyReport to be created earlier or created during function execution. How to deal with this situation?
I have found 2 solutions:
1 Create new DailyReport object before method dispatching, and after that pass reportId to them:
//PHP, simplified
public function __invoke() {
$taskData = getTaskData();
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser('1234-12-12', $user);
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport('1234-12-12', $user);
$dailyReportRepository->store($dailyReport);
}
$result = $dailyReportService->addTaskToDailyReport($taskData, $dailyReport->reportId);
//[...]
}
This one requires to put a more business logic to my Controller which i want to avoid.
2: Verify in method that DailyReport exists, and create new one if needed:
//my controller method
public function __invoke() {
$taskData = getTaskData();
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
//in my service:
public function addTaskToDailyReport($taskData, $date, $user) {
//Ensure that daily report for given day and user exists:
/** #var $dailyReport DailyReport|null **/
$dailyReport = $dailyReportRepository->getOneByDateAndUser();
//there were no report created today, create new one
if($dailyReport === null) {
$dailyReport = new DailyReport($date, $user);
$dailyReportRepository->store($dailyReport);
}
//perform rest of domain logic here
}
This one reduces complexity of my UI layer and does not expose business logic above the Application Layer.
Maybe these example is more CRUD-ish than DDD, but i wanted to expose one of my use-case in simpler way.
Which solution should be used when in these case? Is there any better way to handle get-or-create logic in DDD?
EDIT 2020-03-05 16:21:
a 3 example, this is what i am talking about in my first comment to Savvas Answer:
//a method that listens to new requests
public function onKernelRequest() {
//assume that user is logged in
$dailyReportService->ensureThereIsAUserReportForGivenDay(
$userObject,
$currentDateObject
);
}
// in my dailyReportService:
public function ensureThereIsAUserReportForGivenDay($user, $date) {
$report = getReportFromDB();
if($report === null) {
$report = createNewReport();
storeNewReport();
}
return $report;
}
//in my controllers
public function __invoke() {
$taskData = getTaskData();
//addTaskToDailyReport() only adds the data to summary, does not creates a new one
$result = $dailyReportService->addTaskToDailyReport($taskData, '1234-12-12', $user);
//[...]
}
This will be executed only when user will log in for the first time/user were logged in yesterday but this is his first request during the new day.
There will be less complexity in my business logic, i do not need to constantly checking in services/controllers if there is a report created because this has been executed
previously in the day.
I'm not sure if this is the answer you want to hear, but basically I think you're dealing with accidental complexity, and you're trying to solve the wrong problem.
Before continuing I'd strongly suggest you consider the following questions:
What happens if someone submits the same report twice
What happens if someone submits a report two different times, but in the second one, it's slightly different?
What is the impact of actually storing the same report from the same person twice?
The answers to the above questions should guide your decision.
IMPORTANT: Also, please note that both of your methods above have a small window where two concurrent requests to store the rerport would succeed.
From personal experience I would suggest:
If having duplicates isn't that big a problem (for example you may have a script that you run manually or automatically every so often that clears duplicates), then follow your option 1. It's not that bad, and for human scale errors should work OK.
If duplicates are somewhat of a problem, have a process that runs asynchronously after reports are submited, and tries to find duplicates. Then deal with them according to how your domain experts want (for example maybe duplicates are deleted, if one is newer either the old is deleted or flagged for human decision)
If this is part of an invariant-level constraint in the business (although I highly doubt it given that we're speaking about reports), and at no point in time should there ever be two reports, then there should be an aggregate in place to enforce this. Maybe this is UserMonthlyReport or whatever, and you can enforce this during runtime. Of course this is more complicated and potentially a lot more work, but if there is a business case for an invariant, then this is what you should do. (again, I doubt it's needed for reports, but I write it here in the care reports were used as an example, or for future readers).

Symfony voter and multiples reasons

I will use a simple situation for explain :
I have "news" entity
I have "new categories" entities
In administration, I want to check if I can delete news category.
If you dont have "ROLE_SUPERADMIN", you can't ;
If news category is linked (= used in category), you can't.
When control that ?
If I use Symfony Voters :
class NewsCategoryVoter extends Voter {
....
private function canDelete(NewsCategory $newsCategory, User $user)
{
// Check ROLE and Count in NewsRepository if $newsCategory is used. I have not yet coded this.
return false;
}
I have a problem :
I can't get the reason why he can not remove. In twig and after is_granted('delete', category), idealy :
You can't delete because ...
Can you help me ?
Please, keep in mind that this situation is very simple. In my situation, I have many reasons (> 10) to reject a deletion or modification, almost always because of a relationship in database
Because the Voter is just another service, you can add whatever properties, or other classes/services you want or need to be able to store some sort of reason as to why something did, or did not happen.
public static $reason;
// in the voter, make grant/deny/abstain choices...
if ($user->hasRole('ROLE_SUPER_ADMIN')) {
self::$reason = "is super-admin";
$this->log->debug("FeatureVoter | {$user} is super-admin");
return VoterInterface::ACCESS_GRANTED;
}
// after is_granted()
echo VoterClass::$reason;
I already had logging in the voter, and so some other notification mechanism would be just as easy. Here, I've just added a static variable in the Voter, and can read it out externally. You can trivially make that an array that could be added to, (and cleared before voting started), or noting a reason why something did, or didn't happen in an external service that can be retrieved.

How to use IF statement with $id = Auth::id(); to get users id Laravel?

I've searched everywhere for this and tried to make it happen all day today, but still can't figure it out.
Basically I am trying to use IF statements in routes to display appropriate info to users with appropriate ID.
But I am getting
Function name must be a string
For this
if ($user = $id(8))
I can't figure out how to pass in current users id to that if statement to check it. Sorry if dumb question, but I'm just starting with Laravel.
Route::get('/secret', function()
{
$user = Auth::user();
$id = Auth::id();
if ($user = $id(8))
{
return 'It worked!';
}
return 'Your id is not 8.';
});
Thanks
You cannot use if($user = $id(8)), try to use: if($id == 8) instead. You want to compare two values, so you need to use ==, = means assigning a value.
Hope it works!
By the way: you don't have to use the $user variable as far as I can see. $id is enough in your case ;)
JohnLV's answer is correct, but you look like you might be better off making a route filter - read here for more information:
http://laravel.com/docs/routing#route-filters
This is for when you want to block access to certain pages/routes based on user privileges (i.e. hide admin area from users).
But if you want to change things in you view based on user id, then it is best to put this code in the controller.

proper way to set user's last_visit

What is the proper way to implement updating user's last visit in database?
I have column last_visit in users table and added this code to bootstrap.php file
protected function _initUser() {
$this->bootstrap(array('db'));
$auth = Zend_Auth::getInstance();
$identity = $auth->getIdentity();
if($identity) {
$userModel = new Model_User();
$user_id = $identity->id;
$userModel->updateLastVisit($user_id, date("Y-m-d H:i:s"));
}
}
Is that good? Or should I do this diffrent way?
EDIT
I ended up doing this so the database is queried only once every 5 minutes. Can I leave it as it is now or are there any more changes necessary to make it work better (in performance point of view)
protected function _initUser() {
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
$currentUser = $auth->getIdentity();
if((time() - strtotime($currentUser->last_visit)) > 60*5) {
$last_visit = date("Y-m-d H:i:s");
$currentUser->last_visit = $last_visit;
$this->bootstrap(array('db'));
$user = new Model_User();
$user->updateLastVisit($currentUser->id, $last_visit);
}
}
}
That is Ok if you have no problems with speed. Now you have a bunch of operations for each request (Even if it looks pretty simple - Zend will do a lot of work behind those two lines). And if you fight for tens of milliseconds - these lines are one of the first candidate for improvement. I do not think you need to have the latest possible value. So it can be enough to update last_visit after login or when user is logged in automatically because of "remember me" enabled.
You should perform this kind of operation using plug-in hooks when dispatching the request, the same way you do when checking if the user is logged in. While it works through the bootstrap class, from an architectural point of view, this is related to the cross-cutting concerns of your application (see Aspect-oriented programming).
You can even perform this operation within your access check plug-in, something similar to what #FAngel proposed: Storing the new date after logins or after grabbing the cookie, avoiding requesting the database every time the user reloads the page.
Last login: Update the datetime in your LoginController. Example: https://gist.github.com/2842698 (line 100), or as Keyne already said, put it in a special plug-in.
Last visit: We put this data temporary in a key-value storage(memached), and save the data with a cronjob each 5 minutes. So you don't have any performance issues with your DB.

storing permissions into multi dimensional array php

OK, I am just trying to get better at making more loosely coupled classes etc in PHP just to improve my skills. I have a local test database on my computer and for the user table I have a column named "role". I am trying to build a function that is a general function for getting permissions for a user so it doesn't depend on a specific task they are trying to do.
When a user tries to do something such as create a new forum topic etc, I want to query the database and if "role" is a certain value, store permissions in a multidimensional array like the following:
$permissions = array(
'forums' => array("create", "delete", "edit", "lock"),
'users' => array("edit", "lock")
);
Then I want to be able to search that array for a specific permission without typing the following at the top of every PHP file after a user posts a form by checking isset($var). So if the user is trying to edit a user I want to be able to do something like the following via a class method if possible
if (Class::get_permissions($userID),array($permissionType=>$permission))) {
// do query
} else {
// return error message
}
How would be a good way to have a loosely coupled permission checking function that will be able to do something like this? It doesn't have to be laid out exactly like this but just be loosely coupled so it can be reused and not be bound to a certain task. But I want to be able to have an array of permissions instead of just "admin","user", etc for reusability and so it doesn't restrict my options down the road. Because I have a bunch of code that is like this right now in the top of my php script files.
if (Class::get_permissions($userID) == "admin") {
// allow query
} else {
// return error
}
Thanks for any input to help me get this to where I don't keep writing the same stuff over and over.
Your question is a little vague, but I will do my best. You said you're storing their permissions in an array $permissions.
public static $permissions = array();
public static function setPermissions($perms)
{
if (!is_array($perms)) {
throw new Exception('$perms must be an array.');
}
self::$permissions = $perms;
}
public static function hasPermission($section, $action)
{
if (array_key_exists($section, self::$permissions)
&& in_array($action, self::$permissions[$section])
) {
return true;
}
return false;
}
Using that logic, when you read a user's permissions from the DB, then set the Class::$permissions static var like so:
Class::setPermissions($permissions);
// ...
if (Class::hasPermissions($page, $action)) {
// user has permission
}
Note, my code is pretty generic and will have to remain that way until I have more information. For now, I'm assuming your permissions array is using a page section as the index and the array is a list of actions within that page section that the user has access to. So, assuming $page has been set to something like "forums" and the user is currently trying to perform an edit (so $action = 'edit'), the Class::hasPermission() function would return true.
I ran out of characters in the comments... But this is to your comment.
#corey instead of having a static object, I include a function that sets my permissions in the user's session. It as part of my LoginCommand class that gets called whenever the user logs in, obviously.
The permissions are then stored from view to view and I don't have to keep querying. The permissions check for most things only happen when the user logs in. However, certain sensitive things I'll run another query to double check. This has the disadvantage that, if the user's permissions change while the user has an active session, these changes won't be pushed to the user.
Remember to exercise good session security.
PHP Session Security
The only reason you wouldn't store data in your session size is because your session got too big. But unless you sessions are megabyte's, you probably don't need to worry about this.

Categories