Laravel visitors counter - php

I'm trying to build a visitors counter in Laravel....
I don't know what the best place is to put the code inside so that it loads on EVERY page... But I putted it inside of the routes.php....
I think I'll better place it inside of basecontroller?
But okay, My code looks like this now:
//stats
$date = new \DateTime;
$check_if_exists = DB::table('visitor')->where('ip', $_SERVER['REMOTE_ADDR'])->first();
$get_visit_day = DB::table('visitor')->select('visit_date')->where('ip', $_SERVER['REMOTE_ADDR'])->first();
$value = date_create($get_visit_day->visit_date);
if(!$check_if_exists)
{
DB::table('visitor')->insert(array('ip' => $_SERVER['REMOTE_ADDR'], 'hits' => '1', 'visit_date' => $date));
}else{
DB::table('visitor')->where('ip', $_SERVER['REMOTE_ADDR'])->increment('hits');
}
$value = date_create($get_visit_day->visit_date);
if ($check_if_exists && date_format($value, 'd') != date('d')) {
DB::table('visitor')->insert(array('ip' => $_SERVER['REMOTE_ADDR'], 'hits' => '1', 'visit_date' => $date));
}
That works fine, but the problem is, my database columns always add a new value.
So this is my database:
From the table 'visitor'.
It keeps adding a new IP, hit and visit_date...
How is it possible to just update the hits from today (the day) and if the day is passed, to set a new IP value and count in that column?

I'm not 100% sure on this, but you should be able to do something like this. It's not tested, and there may be a more elegant way to do it, but it's a starting point for you.
Change the table
Change the visit_date (datetime) column into visit_date (date) and visit_time (time) columns, then create an id column to be the primary key. Lastly, set ip + date to be a unique key to ensure you can't have the same IP entered twice for one day.
Create an Eloquent model
This is just for ease: make an Eloquent model for the table so you don't have to use Fluent (query builder) all the time:
class Tracker extends Eloquent {
public $attributes = [ 'hits' => 0 ];
protected $fillable = [ 'ip', 'date' ];
protected $table = 'table_name';
public static function boot() {
// Any time the instance is updated (but not created)
static::saving( function ($tracker) {
$tracker->visit_time = date('H:i:s');
$tracker->hits++;
} );
}
public static function hit() {
static::firstOrCreate([
'ip' => $_SERVER['REMOTE_ADDR'],
'date' => date('Y-m-d'),
])->save();
}
}
Now you should be able to do what you want by just calling this:
Tracker::hit();

Looking at your code and reading your description, I’m assuming you want to calculate number of hits from an IP address per day. You could do this using Eloquent’s updateOrNew() method:
$ip = Request::getClientIp();
$visit_date = Carbon::now()->toDateString();
$visitor = Visitor::findOrNew(compact('ip', 'visit_date'));
$visitor->increment('hits');
However, I would add this to a queue so you’re not hitting the database on every request and incrementing your hit count can be done via a background process:
Queue::push('RecordVisit', compact('ip', 'visit_date'));
In terms of where to bootstrap this, the App::before() filter sounds like a good candidate:
App::before(function($request)
{
$ip = $request->getClientIp();
$visit_date = Carbon::now()->toDateString();
Queue::push('RecordVisit', compact('ip', 'visit_date'));
);
You could go one step further by listening for this event in a service provider and firing your queue job there, so that your visit counter is its own self-contained component and can be added or removed easily from this and any other projects.

Thanks to #Joe for helping me fulley out!
#Martin, you also thanks, but the scripts of #Joe worked for my problem.
The solution:
Tracker::hit();
Inside my App::before();
And a new class:
<?php
class Tracker Extends Eloquent {
public $attributes = ['hits' => 0];
protected $fillable = ['ip', 'date'];
public $timestamps = false;
protected $table = 'visitor';
public static function boot() {
// When a new instance of this model is created...
static::creating(function ($tracker) {
$tracker->hits = 0;
} );
// Any time the instance is saved (create OR update)
static::saving(function ($tracker) {
$tracker->visit_date = date('Y-m-d');
$tracker->visit_time = date('H:i:s');
$tracker->hits++;
} );
}
// Fill in the IP and today's date
public function scopeCurrent($query) {
return $query->where('ip', $_SERVER['REMOTE_ADDR'])
->where('date', date('Y-m-d'));
}
public static function hit() {
static::firstOrCreate([
'ip' => $_SERVER['REMOTE_ADDR'],
'date' => date('Y-m-d'),
])->save();
}
}
Named 'tracker' :)

public $attributes = ['hits' => 0];
protected $fillable = ['ip', 'date'];
public $timestamps = false;
protected $table = 'trackers';
public static function boot() {
// When a new instance of this model is created...
parent::boot();
static::creating(function ($tracker) {
$tracker->hits = 0;
} );
// Any time the instance is saved (create OR update)
static::saving(function ($tracker) {
$tracker->visit_date = date('Y-m-d');
$tracker->visit_time = date('H:i:s');
$tracker->hits++;
} );
}
// Fill in the IP and today's date
public function scopeCurrent($query) {
return $query->where('ip', $_SERVER['REMOTE_ADDR'])
->where('date', date('Y-m-d'));
}
public static function hit() {
/* $test= request()->server('REMOTE_ADDR');
echo $test;
exit();*/
static::firstOrCreate([
'ip' => $_SERVER['REMOTE_ADDR'],
'date' => date('Y-m-d'),
// exit()
])->save();
}
In laravel 5.7 it required parent::boot() otherwise it will show Undefined index: App\Tracker
https://github.com/laravel/framework/issues/25455

This is what i did its very basic but can easily build it up and add filters on visitors per day month year etc..
i added the following code to the web.php file above all the routes to run on each request on the site so no matter what page the visitor landed on it will save the ip addess to the database only if its unique so one visitor wont keep addding to the visitor count
// Web.php
use App\Models\Visitor
$unique_ip = true;
$visitors = Visitor::all();
foreach($visitors as $visitor){
if($visitor->ip_address == request()->ip()){
$unique_ip = false;
}
}
if($unique_ip == true){
$visitor = Visitor::create([
'ip_address' => request()->ip(),
]);
}
Routes...
the model is straight forward just has a ip addess field

Related

Laravel timestamps keep track of who has done certain action (like created_at & created_by, updated_at & updated_by)

In my application I want to keep track of who has performed certain operations on different models in my application.
Default Laravel model with timestamps automatically updates fields like created_at and updated_at. I can modify this behavior to set the created_by field automatically by calling the static::updating() function as mentioned in this answer: https://stackoverflow.com/a/64241347/4112883 . This works very well. Additionally, I came across this package (https://github.com/WildsideUK/Laravel-Userstamps), but that is limited to only created, updated, and deleted.
For my Post model, I have more timestamps: created_at, updated_at, completed_at, checked_at, and published_at. When a user ends the post, it must be verified by that user's manager. If all is well, some logic will publish the message, but if not, the manager can create one or more actions for the user to complete the message, which will undo the finishing attributes. An action is created with the following timestamps: created, updated, and completed (null). When the user completes an action, the actions.finished_at and actions.finished_by fields are set.
Now comes the challenge. For each custom timestamp, I want to set the relationship and three functions to handle certain states of the timestamp: set, undo and check for isset:
class Post extends Model
{
//…
public function finishedBy() //relationship belongsTo User::class
{
return $this->belongsTo(User::class, 'finished_by');
}
public function finish() { //function to finish post (SET)
$this->update([
'finished_by' => auth()->id(),
'finished_at' => now(),
]);
}
public function undoFinish() { //function to undo finishing (UNSET)
$this->update([
'finished_at' => null,
'finished_by' => null,
]);
}
public function isFinished() { //function to check if is finished (ISSET)
return !empty($this->finished_by) && !empty($this->finished_at);
}
//…
All four functions must be repeated for ‘checked’ and ‘published’ in the Post model, and for the ‘finished’ attribute in Action model, leading to a lot of almost-duplicate code. (Maybe in the future I want to repeat this logic in other models.)
Is there a possibility to make this more elegant with a Trait or something?
E.g. create something like an protected array $timestamps_with_user by which the application automatically adds the relationship and the three functions?
protected $timestamps_with_users = [
'finish', 'check', 'publish'
];
// foreach in a trait?? Need your help here :D
foreach($timestamps_with_users as $perform) {
public function $perform() { … } //$post->finish()
public function $perform.edBy() :User { … } //$post->finishedBy()
public function undo.$perform() { … } //$post->undoFinish()
public function is.$perform.ed() { … } //$post->isFinished()
}
Thanks in advance and looking forward to your answers.
Just create a new trait and create functions that works with any timestamp:
<?php
namespace App\Traits;
trait CustomTimestamps {
public function perform(string $action)
{
$this->update([
$action . 'ed_by' => auth()->id(),
$action . 'ed_at' => now(),
]);
}
public function undo(string $action)
{
$this->update([
$action . 'ed_by' => null,
$action . 'ed_at' => null,
]);
}
public function check(string $action)
{
$at = $action . 'ed_at';
$by = $action . 'ed_by';
return !empty($this->{$by}) && !empty($this->{$at});
}
}

A two digit month could not be found Data missing in Carbon in Laravel

I'm building a small application on Laravel 5.4 I'm trying to receive dates from a datepicker widget from front end and parsing it into Carbon date format something like this:
Carbon\Carbon::parse($request->schedule)->toDateTimeString();
In continuation with my previous questions: How to format date receiving through Vuejs Datepicker in laravel , I successfully added this to my database, and while calling it I'm placing an accessor in my model and trying to fetch the date in diffForHumans() format which was my previous question: A two digit month could not be found Data missing in Laravel , this works perfectly fine whenever I'm fetching the model, until I'm not assigning this schedule attribute to any other variable, Now while retrieving the models in the controller and assigning to the a value with something like this:
public function getData()
{
$user = Auth::user();
$summaries = InteractionSummary::all();
$meetings = [];
foreach($summaries as $summary)
{
$company = [];
$tempData = $summary->interaction->where('user_id', $user->id)->get()->first();
if($tempData)
{
$meeting = Interaction::find($tempData->id);
$tempData->schedule = $meeting->schedule;
$meetings[] = $tempData;
}
}
return response()->json(['meetings' => $meetings], 200);
}
I'm getting the same error:
A two digit month could not be found Data missing
And the same works perfectly fine if I do:
public function getFutureData()
{
$user = Auth::user();
$currentTime = Carbon::now();
$interactions = $user->interaction->where('schedule', '>=', $currentTime);
$meetings = [];
foreach($interactions as $interaction)
{
$interaction->meeting = $meeting;
$meetings[] = $interaction;
}
return response()->json(['interactions' => $meetings], 200);
}
In my model with the name: Interaction I'm defining my attribute something like this:
public function getScheduleAttribute($value)
{
return Carbon::parse($value)->diffForHumans();
}
EDIT
FYI: I'm having my InteractionSummary model and have following relationship:
public function interaction()
{
return $this->belongsTo('App\Interaction');
}
Most certainly your problem is caused by this line:
$tempData->schedule = $meeting->schedule;
The reason is, that you have setup date columns (see linked question) like this:
protected $dates = [
'schedule', 'created_at', 'updated_at', 'deleted_at'
];
So 'schedule' is treated as a date.
When you retrieve your schedule date via $meeting->schedule The value is modified by your mutator and ends up with something like '1 hour ago'.
So with $tempData->schedule = $meeting->schedule; you are in fact trying to set an invalid date for $tempData->schedule . It would translate as
$tempData->schedule = '1 hour ago';
As you marked 'schedule' as a date in your dates array Laravel is trying to parse it with Carbon. It expects to have dates in the format that is specified in your model's $dateFormat attribute (defaulting to Y-m-d H:i:s).
As #shock_gone_wild said, if you have 'schedule' inside your $dates array,
protected $dates = [
'schedule', 'created_at', 'updated_at', 'deleted_at'
];
Laravel will give you a Carbon\Carbon instance.
If that's your case, you should format before sending it (otherwise it will be a Carbon object).
$tempData->schedule = $meeting->schedule->format('Y-m-d');
Try this:
public function getData()
{
$user = Auth::user();
$summaries = InteractionSummary::all();
$meetings = [];
foreach($summaries as $summary)
{
$company = [];
$tempData = $summary->interaction->where('user_id', $user->id)->get()->first();
if($tempData)
{
$meetings[] = $tempData;
}
}
return response()->json(['meetings' => $meetings], 200);

Laravel: Updating hasMany/BelongTo relation

I have a master table jobs with multiple location in separate table job_location. Now I am not able to update/delete, if extra rows found from job_location. Now why I am saying DELETE is because sync() did this, but it's related to many-to-many relation. I am new to laravel, just trying to get eloquent approach to achieve this, otherwise deleting all rows and inserting can be done easily OR updating each and delete remaining is also an option but I wonder Laravel has something for this.
In every request I get multiple job locations(with unchanged/changed city,phone_number,address) which is creating trouble.
Some codeshots:
Model: [Job.php]
class Jobs extends Model
{
protected $fillable = [
'job_id_pk', 'job_name','salary'
];
public function joblocation() {
return $this->hasMany('\App\JobLocation', 'job_id_fk', 'job_id_pk');
}
}
Model:[JobLocation.php]
class JobLocation extends Model
{
protected $fillable = [
'jobl_id_pk', 'job_id_fk','city', 'address', 'phone_number'
];
public function job() {
return $this->belongsTo('\App\Jobs', 'job_id_fk', 'job_id_pk');
}
}
Controller:[JobController.php]
function jobDetail() {
if($params['jid']) {
// update
$obj = \App\Jobs::find($params['jid']);
$obj->job_name = $params['name'];
$obj->salary = $params['salary'];
$obj->save();
} else {
// create new
$data = array(
'job_name' => $params['name'],
'salary' => $params['salary'],
);
$obj = \App\Jobs::create($data);
}
// don't bother how this $objDetail has associative array data, it is processed so
foreach ($params['jobLocations'] AS $key => $objDetail) {
$jobLoc = new \App\JobLocation;
$jobLoc->city = $objDetail['city'];
$jobLoc->phone_number = $objDetail['phone_number'];
$jobLoc->address = $objDetail['address'];
$jobLoc->job()->associate($obj);
$obj->jobLoc()->save($jobLoc);
}
}
In this approach I am able to save all job locations, but I am using same function to update also. Please tell how I can update jobLocations if present. I am ok to loose previous entries, but it would be good if previous gets updated and new get entered OR if we have extra entries they get deleted. I know sounds weird but still guide me a way.
Yea, you cannot use the same function, do this
$jobs = \App\Jobs::find($params['jid']);
foreach ($params['jobLocations'] as $key => $objDetail) {
$joblocation = $jobs->joblocation->where('jobl_id_pk', $objDetail['some_id'])->first();
//here update you job location
$joblocation->save();
}
Something like this:
Controller:[JobController]
public function jobDetail() {
if( !empty($params['jid']) ) {
// update
$job = \App\Jobs::find($params['jid']);
$job->job_name = $params['name'];
$job->salary = $params['salary'];
$job->save();
} else {
// create new
$data = array(
'job_name' => $params['name'],
'salary' => $params['salary'],
);
$job = \App\Jobs::create($data);
}
$locationDetails = !empty($params['jobLocations']) ? $params['jobLocations'] : [];
$jobLocations = array_map(function($location) use($job) {
$location = array_merge($location, [ 'job_id_fk' => $job->job_id_pk ]);
return \App\JobLocation::firstOrNew($location);
}, $locationDetails);
$job->jobLocations()->saveMany($jobLocations);
}

How to insert create date, modified date and user id by default on insert in yii

I am working on yii and want a functionality to auto insert created , modified and user_id(my column names in db). I am currently doing this with following way . I have to add this code in every model .
public function rules()
{
return array(
......
array('created, modified', 'default', 'value'=>new CDbExpression('NOW()'), 'setOnEmpty' => false, 'on' => 'insert'),
array('modified', 'default', 'value' => new CDbExpression('NOW()'), 'setOnEmpty' => false, 'on' => 'update'),
array('user_id', 'default', 'value' => Yii::app()->user->id, 'setOnEmpty' => false,'on' => 'insert'),
array('id, feed_id, user_id, text, created, modified', 'safe', 'on'=>'search'),
..........
);
}
this is working on insert and update, But I want is
That if here is a method so that i have to insert it in one file and
no need to insert this in every model . If it is possible
If you have several models and want to implement common behaviour on them, you can use a custom component and use any of given methods in comments and other answers (behaviors, rules, beforeSave, etc) and extending it for all models.
Create a new file in protected/components called for example MasterModel.php. In this example I want to inherit beforeSave method for all models. Fill MasterModel.php with:
<?php
abstract class MasterModel extends ActiveRecord
{
public function beforeSave()
{
$current_time = date('Y-m-d H:i:s');
if ( $this->isNewRecord )
{
$this->created = $current_time;
$this->created_by = Yii::app()->user->id;
}
if ( ! $this->isNewRecord )
{
$this->updated = $current_time;
$this->updated_by = Yii::app()->user->id;
}
return parent::beforeSave();
}
}
Replace on all your existing and future model definitions:
<?php
class Client extends ActiveRecord
{
....
With:
<?php
class Client extends MasterModel
{
....
Make sure to have on your database tables and models:
created DATETIME
created_by INT
updated DATETIME
updated_by INT
You can do this in three ways:
1) Update via model’s rules:
public function rules()
{
return array(
array('title','length','max'=>255),
array('title, created_at, updated_at', 'required'),
array('updated_at','default',
'value'=>new CDbExpression('NOW()'),
'setOnEmpty'=>false,'on'=>'update'),
array('created_at,updated_at','default',
'value'=>new CDbExpression('NOW()'),
'setOnEmpty'=>false,'on'=>'insert'),
array('user_id','default',
'value'=> Yii::app()->user->id,
'setOnEmpty'=>false,'on'=>'insert')
);
}
2) Another to use beforeSave() as follows:
public function beforeSave() {
if ($this->isNewRecord)
$this->created_at = new CDbExpression('NOW()');
$this->user_id = Yii::app()->user->id;
$this->updated_at = new CDbExpression('NOW()');
return parent::beforeSave();
}
3) Another alternative to use CTimestampBehavior in your models:
public function behaviors()
{
return array(
'CTimestampBehavior'=>array(
'class'=>'zii.behaviors.CTimestampBehavior',
'createAttribute'=>'created_at',
'updateAttribute'=>'updated_at',
'setUpdateOnCreate'=>true,
'timestampExpression'=>new CDbExpression('NOW()');
)
);
}
Make sure your every table has same field name created_at, updated_at and user_id.
I found a very usefull article here how to insert autofill yii model data
you just need to create a class as #Alejandro Quiroz answered . The issue in that answer was if field is not available it throws an exception so here is the best solution i found. you need to check if attribute available with if($this->hasAttribute('modified'))
public function beforeSave()
{
$current_time = date('Y-m-d H:i:s');
if ( $this->isNewRecord )
{
if($this->hasAttribute('created'))
$this->created = $current_time;
if($this->hasAttribute('modified'))
$this->modified = $current_time;
if($this->hasAttribute('user_id')) // make sure user field name i user_id
$this->user_id = Yii::app()->user->id;
}
if ( ! $this->isNewRecord )
{
if($this->hasAttribute('modified'))
$this->modified = $current_time;
/* remove this if want updated by id */
//$this->updated_by = Yii::app()->user->id;
}
return parent::beforeSave();
}

Codeigniter Unit-testing models

I'm new to unit-testing, so this is maybe a little dumb question.
Imagine, we have a simple model method.
public function get_all_users($uid = false, $params = array()){
$users = array();
if(empty($uid) && empty($params)){return $users;}
$this->db->from('users u');
if($uid){
$this->db->where('u.id',(int)$id);
}
if(!empty($params)){
if(isset($params['is_active']){
$this->db->where('u.status ', 'active');
}
if(isset($params['something_else']){ // some more filter actions}
}
$q = $this->db->get();
if($q->num_rows()){
foreach($q->result_array() as $user){
$users[$user['id']] = $user;
}
}
$q->free_result();
return $users;
}
The question is how a _good test would be written for it?
UPD: I guess, the best unit-testing library for CI is Toast, so example i'm looking for, preferable be written using it.
Thanks.
I'm using toast too, and mostly I use it to test a model methods. To do it, first truncate all table values, insert a predefined value, then get it. This is the example of test I used in my application:
class Jobads_tests extends Toast
{
function Jobads_tests()
{
parent::Toast(__FILE__);
// Load any models, libraries etc. you need here
$this->load->model('jobads_draft_model');
$this->load->model('jobads_model');
}
/**
* OPTIONAL; Anything in this function will be run before each test
* Good for doing cleanup: resetting sessions, renewing objects, etc.
*/
function _pre()
{
$this->adodb->Execute("TRUNCATE TABLE `jobads_draft`");
}
/**
* OPTIONAL; Anything in this function will be run after each test
* I use it for setting $this->message = $this->My_model->getError();
*/
function _post()
{
$this->message = $this->jobads_draft_model->display_errors(' ', '<br/>');
$this->message .= $this->jobads_model->display_errors(' ', '<br/>');
}
/* TESTS BELOW */
function test_insert_to_draft()
{
//default data
$user_id = 1;
//test insert
$data = array(
'user_id' => $user_id,
'country' => 'ID',
'contract_start_date' => strtotime("+1 day"),
'contract_end_date' => strtotime("+1 week"),
'last_update' => time()
);
$jobads_draft_id = $this->jobads_draft_model->insert_data($data);
$this->_assert_equals($jobads_draft_id, 1);
//test update
$data = array(
'jobs_detail' => 'jobs_detail',
'last_update' => time()
);
$update_result = $this->jobads_draft_model->update_data($jobads_draft_id, $data);
$this->_assert_true($update_result);
//test insert_from_draft
$payment_data = array(
'activation_date' => date('Y-m-d', strtotime("+1 day")),
'duration_amount' => '3',
'duration_unit' => 'weeks',
'payment_status' => 'paid',
'total_charge' => 123.45
);
$insert_result = $this->jobads_model->insert_from_draft($jobads_draft_id, $payment_data);
$this->_assert_true($insert_result);
//draft now must be empty
$this->_assert_false($this->jobads_draft_model->get_current_jobads_draft($user_id));
}
}
I'm using AdoDB in my application, but don't get confuse with that. You can do $this->db inside the test controller, after you load the database library. You can put it in autoload so it will automatically loaded.
See that in my code, before the test is run, the table is truncated. After run, I will get any error that might occured. I do assert for a predefined insert and update. Using Toast to test the model will make you sure that the model's method doing exactly the task that you want it to do. Make the test that you need, and make sure you cover all the possibilities of input and output values.

Categories