proper way to set user's last_visit - php

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.

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).

How to create user activity logs?

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.

How can I test if I have all the right credentials with php-opencloud sdk?

Here is my code for now:
$cloud = new Rackspace('https://identity.api.rackspacecloud.com/v2.0/', $php_cloudconfig['credentials']);
$array_creds = getCredentials();
$cloud->ImportCredentials($array_creds);
$array_creds = $cloud->ExportCredentials();
setCredentials($array_creds['authorization_token'], $array_creds['expiration'], $array_creds['tenant_id'], $array_creds['service_catalog']);
function getCredentials() {
$sql_get_credential = "SELECT * FROM cloud_apiconnection";
$q = $conn->prepare($sql_get_credential);
return $q->execute();
}
function setCredentials($authorization_token, $expiration, $tenant_id, $service_catalog) {
$sql_insert = "INSERT INTO cloud_apiconnection (authorization_token, expiration, tenant_id, service_catalog) VALUES (:authorization_token, :expiration, :tenant_id, :service_catalog)";
$q = $conn->prepare($sql_insert);
$q->execute(array(':authorization_token' => $authorization_token, ':expiration' => $expiration, ':tenant_id' => $tenant_id, ':service_catalog' => $service_catalog));
}
Is there a way to detect if the credentials were updated in: $cloud->ImportCredentials($array_creds); ?
I am wandering because I don't want to write to the DB if I don't need to.
Also is this the best strategy for managing my connection to RackSpace API?
It seems like a good strategy for maintaining a persistent session, because you're re-using an existing token ID. The only other suggestion is to cache your credentials in a local file, rather than making an MySQL transaction. You don't really need to store your tenant ID and service catalog because these are easily retrievable through the software layer.
To check the validity an existing token, I'd do this:
$connection = new Rackspace(...);
// Import existing credentials
$credentials = getCredentials();
$connection->importCredentials($credentials);
// Authenticate against the API to make sure your token is still valid
$connection->authenticate();
// Compare result
$newCredentials = $connection->exportCredentials();
if ($newCredentials['authorization_token'] != $credentials['authorization_token']) {
// You know it's been updated, so save new ones
setCredentials();
}
All the authenticate() method does is execute a request against the Rackspace API; and based on the results, it's effectively letting you know whether your existing stuff is still valid. When other methods call authenticate(), they usually do another check beforehand: they check the expiry value (i.e. not in the past). You could implement the same thing they do (to find out whether credentials need to be refreshed and saved):
if (time() > ($credentials['expiration'] - RAXSDK_FUDGE)) {
// They're old, you need to call authenticate()
} else {
// They seem to be still valid. Sweet!
}
By the way, we've recently changed this functionality so that exportCredentials() makes a call to authenticate() - so this would mean you wouldn't need to call it yourself. But for your current version it's worth leaving it in.
Does that answer everything?

Site Design: How to award users tasks with achievements?

So I'm wanting to setup an achievements system on my site. People perform tasks and upload this information which is then stored in a database (think 'time', 'date', 'task', etc.). What would be the best method of checking their information and awarding achievements? Would I just have like an achievement.php that once information is uploaded it would trigger this document to run through all the checks to determine if the user needs to be awarded an achievement? Or is there something server side I should set up to award the user?
Thanks for any help or suggestions, comments, etc. :D
EDIT: I currently have the achievements listed in the database, (id, name, class)
Tasks are stored as ('date_time','time','device','user_id[fk]')
EDIT 2: Also many achievements will be calculated based on not only the tasks the user is currently submitting but takes into account previous tasks in addition to the newly added task. EX: If the user has completed 3 tasks within 3 consecutive days, then they will be awarded for it
Your best bet is probably to create a table of point values for the tasks, and then create a stored procedure that can fetch the appropriate counts from the appropriate tables and multiply them by the point values. That's what I've done in the past - it allows you modify point values on the fly from the DB as well.
it really depends on where your preference for business logic placement lies, and how real time you want acheivements to be. if you're looking to offload a bunch of business logic on you sql server, put it in a stored procedure, otherwise, class out the calculations into a class in php, and use that class to determine what new achievements have been.
i would definitely suggest doing the processing outside of the normal page response. perhaps kick off a server-side call to the php cli, or set up a cron job to run all individuals through a check for achievements at a certain interval.
edit:
as for the actual methods of awarding achievements, i would think you're most flexible and simple implementation (you will find more simple/less flexible and more flexible/less simple options i'm sure) would be to create an AwardRunner class, an IAward interface and a bunch of individual implementations of IAward for each award you have. the basic idea would be something like:
<?php
class AwardRunner {
var $UserId = 0;
function AwardRunner($userId) {
$this->UserId = $userId;
$dir = "/path/to/your/folder/full/of/IAwards/";
$includes = read_dir($dir);
//include all files that exist
foreach($includes as $include)
{
if (is_file($include))
{
require($include);
}
}
}
public function Run() {
$classList = get_declared_classes();
foreach($classList as $key => $className)
{
if (in_array('IAward', class_implements($className))) {
$award = $className();
$award->UserId = $this->UserId;
$award->GrantIfUserQualifies();
}
}
}
//function for reading all files in a directory.
//this is recursive, so any files in subfolders will also make it in
function read_dir($dir)
{
$array = array();
$d = dir($dir);
while (false !== ($entry = $d->read())) {
if($entry!='.' && $entry!='..') {
$entry = $dir.'/'.$entry;
if(is_dir($entry)) {
$array = array_merge($array, read_dir($entry));
} else {
$array[] = $entry;
}
}
}
$d->close();
return $array;
}
}
?>
i would think the idea of what the IAward interface would look like would be pretty clear from the usage, though you'd probably add to it the Id field from your table so it would be able to insert itself into the database, as would the way to call the AwardRunner class.
this idea should work whether you have something batching the awards process looping through all your users, or just fire it off after every task submission.
How about you create a trigger on the task submission proc (or however you insert the data when the user completes a task), that then performs the necessary actions for that user to determine if he/she is awarded an achievement, and then updates the achievements table accordingly.
Then, every-time you load up the information for the user on the front end, the data will already be in for him/her in the achievements table, and you can directly access it (which I'm sure you already do).

Updating Zend_Auth_Storage after edit users profile

I have following situation:
I have loged user, standard authentication with DB table
$authAdapter = new Zend_Auth_Adapter_DbTable(Zend_Db_Table::getDefaultAdapter());
$authAdapter->setTableName('users');
$authAdapter->setIdentityColumn('user_name');
$authAdapter->setCredentialColumn('password');
When user edits his profile, I save it into Db, but I need to update also storage (using standard Zend_Auth_Storage_Session). Is there any easy way how to do it? Many thanks.
$user = Zend_Auth::getInstance()->getIdentity();
$user->newValue = 'new value';
Assuming you are updating the session data in the same statement you are updating the database in there is no need to call the db again.
Your best bet would be to not use Zend_Auth's storage to hold information that's likely to change - by default it only holds the identity (for good reason). I'd probably make a User class that wrapped all the Auth, ACL (and probably profile) functionality that uses a static get_current() method to load the user stashed in the session. This bypasses all the consistency issues you run into when stuffing it into the session as well as giving you a single point from which to implement caching if/when you actually need the performance boost.
I have done it like this, it works, but I don't know if there is not some better way,how to do it
$user_data = User::getUser($user_id)->toArray();
unset($user_data['password']);
$std_user = new stdClass();
foreach ($user_data as $key => $value)
{
$std_user->$key = $value;
}
$auth = Zend_Auth::getInstance();
$auth->getStorage()->write($std_user);

Categories