shetabit / visitor
Laravel visitor
Requires
- php: ^8.4
- illuminate/database: ^12.0|^13.0
- illuminate/http: ^12.0|^13.0
- illuminate/routing: ^12.0|^13.0
- illuminate/support: ^12.0|^13.0
- jaybizzle/crawler-detect: ^1.4
- mobiledetect/mobiledetectlib: ^4.11
- ua-parser/uap-php: ^3.10
Requires (Dev)
- geoip2/geoip2: ^3.4
- larastan/larastan: ^3.10
- orchestra/testbench: ^10.0|^11.0
- phpcsstandards/php_codesniffer: ^4.0
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^11.5|^12.0|^13.0
- rector/rector: ^2.6
- stevebauman/location: ^7.6
Suggests
- geoip2/geoip2: Needed by the MaxMind resolver that enriches a visit with the location of its IP, read from a GeoIP2 or GeoLite2 City database
- stevebauman/location: Needed by the GeoIP resolver that enriches a visit with the location of its IP
Provides
None
Conflicts
None
Replaces
None
- dev-master
- v5.2.1
- v5.2.0
- v5.1.1
- v5.1.0
- v5.0.0
- v4.5.6
- v4.5.5
- v4.5.4
- v4.5.3
- v4.5.2
- v4.5.1
- v4.5.0
- v4.4.2
- v4.4.1
- v4.4.0
- v4.3.1
- v4.3.0
- v4.2.0
- v4.1.0
- v4.0.1
- v4.0.0
- v3.1.4
- v3.1.3
- v3.1.2
- v3.1.1
- v3.1.0
- v3.0.1
- v3.0
- 2.2.0
- v2.1.1
- v2.1
- v2.0.1
- v2.0
- v1.0
- dev-fix/maxmind-missing-database
- dev-feat/maxmind-resolver
- dev-docs/readme-features
- dev-feat/decoded-url
- dev-feat/visits-connection
- dev-feat/custom-visit-model
- dev-feat/route-name
This package is auto-updated.
Last update: 2026-10-25 11:19:21 UTC
README
Laravel Visitor
This is a laravel package to extract and access visitors' information such as browser, ip, device and etc.
In this package, you can recognize online users and determine if a user is online or not
This package requires PHP 8.4+ and supports Laravel 12+.
Features
- Visitor's information: browser, platform, device, languages, IP, user agent and the route of the
request, through
visitor()or$request->visitor() - Visit logs of pages, of models and of who visited them, with their visits and unique visitors counted
- Route names kept with every visit, so the visits of a route can be filtered by it
- Readable urls of Persian, Arabic and other pages, decoded for showing them
- Online users: who is online, and whether a given user is
- Automatic logging of every request, and of the models the router bound, by a middleware
- GeoIP enrichment: the country, region, city and coordinates of a visit's IP, through stevebauman/location or a MaxMind database
- Extra attributes: data of your own kept beside a visit, and filtered by
- Custom visit model with relations, scopes, accessors and casts of your own
- Database connection and multi-tenancy: the visits kept in a database of their own, the tenant's for instance
- Two user agent parsers to choose from, and paths that are not logged
Install
Via composer
composer require shetabit/visitor
Configure
The service provider and the Visitor alias are registered by Laravel's package discovery, so there is nothing to add
to config/app.php (or to bootstrap/providers.php).
Run the below commands to publish the migrations and create the tables
php artisan vendor:publish --tag=visitor-migrations php artisan migrate
The configuration file can be published as well, if you want to change the driver, the name of the table or the paths that are not logged
php artisan vendor:publish --tag=visitor-config
How to use
You can access to visitor's information using $request->visitor() in your controllers , and you can access to the visitor's information using visitor() helper function any where.
We have the below methods to retrieve a visitor's information:
device: device's nameplatform: platform's namebrowser: browser's namelanguages: language's nameip: client's iprequest: the whole request inputsroute: the route the request matchedrouteName: the name of the route the request matcheduseragent: the whole useragentisOnline: determines if current (or given) user is online
$request->visitor()->browser(); // firefox $request->visitor()->visit($post); // create log for post $request->visitor()->setVisitor($user)->visit($post); // create a log which says $user has visited $post
Store Logs
You can create logs using the visit method like the below
visitor()->visit(); // create a visit log
use Shetabit\Visitor\Traits\Visitable trait in your models, then you can save visit's log for your models like the below
// or you can save log like the below visitor()->visit($model); // or like the below $model->createVisitLog(); // you can say which user has visited the given $model $model->createVisitLog($user); // or like the below visitor()->setVisitor($user)->visit($model);
The visits of a model can be loaded using its visitLogs relation.
You can count model visits like the below
$model->visitLogs()->count();
unique visitors can be counted by their IP and by the model they were signed in as.
// by ip $model->visitLogs()->distinct('ip')->count('ip'); // by user's model $model->visitLogs()->distinct('visitor_id')->count('visitor_id');
Every visit keeps the name of the route it matched in its route_name column (null for a route without a name), so
the visits of a route can be filtered by it
use Shetabit\Visitor\Models\Visit; Visit::query()->where('route_name', 'posts.show')->count(); // visits of the posts.show route visitor()->routeName(); // the name of the route of the current request visitor()->route(); // the route of the current request
The name is the one the route had when it was visited. If you rename a route, rename it in the visits it already has as well, so they keep being found under it
Visit::query()->where('route_name', 'posts.show')->update(['route_name' => 'articles.show']);
The url of a visit is kept as the request had it, so the characters of a Persian or Arabic page, among others, are
percent-encoded in it (/products/%D9%85%D8%AD%D8%B5%D9%88%D9%84). Decoding it before keeping it would lose what tells
an encoded /, & or = apart from a real one, so it is decoded for showing it instead, by decoded_url
$visit->url; // https://example.com/products/%D9%85%D8%AD%D8%B5%D9%88%D9%84 $visit->decoded_url; // https://example.com/products/محصول
use Shetabit\Visitor\Traits\Visitor in your User class, then you can run below codes
$user->visit(); // create a visit log $user->visit($model); // create a log which says, $user has visited $model
Retrieve and Determine Online users
use Shetabit\Visitor\Traits\Visitor in your User class at first.
Then you can retrieve online users which are instance of User class and determine if a user is online.
visitor()->onlineVisitors(User::class); // returns collection of online users User::online()->get(); // another way visitor()->isOnline($user); // determines if the given user is online $user->isOnline(); // another way
Automatic logging
Your application can store visitor's log automatically using LogVisits middleware.
Add the Shetabit\Visitor\Middlewares\LogVisits middleware if you want to save logs automatically.
The middleware will store logs for models which has binded in router (router model binding) and has used Shetabit\Visitor\Traits\Visitable trait.
It has to run after Laravel's SubstituteBindings middleware, which is what turns a route parameter into a model —
appending it to the web group takes care of that.
GeoIP enrichment
A visit can carry the location of the IP it came from, in its geo_raw column. It is disabled by default. To enable it,
install stevebauman/location and turn it on in config/visitor.php
composer require stevebauman/location
'geoip' => true,
The bundled resolver hands the country, the region, the city and the coordinates of a visit over. You can write your
own by implementing Shetabit\Visitor\Contracts\GeoIpResolver and adding it to the resolvers array of the config.
MaxMind
The maxmind resolver reads the location from a GeoIP2 or GeoLite2 City database
of your own, through geoip2/geoip2, so no request leaves your application.
composer require geoip2/geoip2
'geoip' => true, 'resolver' => 'maxmind', 'maxmind' => [ 'database' => storage_path('app/geoip/GeoLite2-City.mmdb'), // where you keep the downloaded database 'locales' => ['en'], // the languages of the country, region and city names, by preference ],
The database is opened at the first lookup and kept open for the life of the application, so a long-running worker, such as an Octane one, keeps reading the copy it opened until it is restarted. MaxMind updates its databases regularly, and geoipupdate can keep yours up to date.
To ask MaxMind's web service instead of a database, bind the resolver to its client in a service provider of your application
use GeoIp2\WebService\Client; use Shetabit\Visitor\Resolvers\GeoIp\MaxMindResolver; $this->app->singleton(MaxMindResolver::class, fn () => new MaxMindResolver(new Client($accountId, $licenseKey)));
An IP the database does not hold, such as a private one, leaves the location of its visit empty. So does a database that is missing or cannot be read, on a fresh server that has not downloaded it yet for instance, or a web service that cannot answer: the visit is still logged, and the error is reported to your exception handler, which logs it. The resolver tries to open the database again at the next visit, so it picks it up as soon as it is in place.
Extra attributes
A visit can keep data of your own beside it, a name or a mobile number for instance, without a column for each of them:
hand them over as key and value pairs when the visit is logged, and they are kept in its extra_attributes JSON column
visitor()->visit($post, ['name' => $request->input('name'), 'mobile' => $request->input('mobile')]); $post->createVisitLog($user, ['mobile' => $request->input('mobile')]); $user->visit($post, ['mobile' => $request->input('mobile')]);
They are read back as an array, and the visits can be filtered by them with Laravel's JSON path syntax
$visit->extra_attributes['mobile']; Visit::query()->where('extra_attributes->mobile', '09120000000')->get();
They never take the place of the columns the package fills: an url among them is kept in extra_attributes, beside
the visit's own url.
Custom visit model
The visits can be logged with a model of your own, to give them relations, scopes, accessors or casts of your application. Extend the package's model
namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Shetabit\Visitor\Models\Visit as BaseVisit; class Visit extends BaseVisit { protected function mobile(): Attribute { return Attribute::get(fn () => $this->extra_attributes['mobile'] ?? null); } }
and name it in config/visitor.php
'model' => \App\Models\Visit::class,
The visitor, the visitLogs and visits relations and the online checks all use the model of the configuration, so
$post->visitLogs()->first()->mobile works. It has to extend Shetabit\Visitor\Models\Visit.
Database connection and multi-tenancy
The visits are kept in the default database connection. To keep them in another one, the tenant connection of a
multi-tenant application for instance, name it in config/visitor.php
'connection' => 'tenant',
The visit model, a model of your own that extends it among them, and the package's migrations all use it: the
migrations create the visits table in that connection. Migrations you published before this setting existed do not
know it, so publish them again, or add a getConnection() that answers with the connection to the copies you have.
With a tenancy package that makes the tenant's connection the default one while it is active, stancl/tenancy's
multi-database mode for instance, leave it null.
The online() scope and isOnline() look the visits up together with the table of the visitors, so the visitors (your
User model, for instance) have to be in the same database as the visits for them.
Testing
Every pull request and every push to master is checked by GitHub Actions: the test suite runs on
PHP 8.4 and 8.5, against Laravel 12 and 13 and against both the lowest and the highest supported dependencies, the
coding style is checked with PHP_CodeSniffer, the sources are analysed with PHPStan (level 7, with larastan) and the
code coverage of the test suite is measured and has to stay above 95%.
The suite has two parts: tests/Unit covers the classes of the package on their own, and tests/Feature sends real
requests through the framework — the middleware, the request macro, the online visitors and the GeoIP enrichment.
You can run the same checks locally. With PHP and Composer installed on your machine:
composer install composer test # run the test suite composer test-coverage # run the test suite and report code coverage composer check-style # check the coding style composer fix-style # fix the coding style where possible composer analyse # run static analysis composer ci # run all of the checks above
If you would rather not install PHP on your machine, the shipped Dockerfile and Makefile run everything inside a
container:
make test # run the test suite make coverage # run the test suite and report code coverage make check-style # check the coding style make fix-style # fix the coding style where possible make analyse # run static analysis make ci # run all of the checks above make shell # open a shell inside the container make help # list every available target
Another PHP version can be used with make test PHP_VERSION=8.5, and a single Laravel version with
make test-laravel LARAVEL=12.
