cakephp3.0 query Builder Retrieve data - query-builder

Hey I am facing some problem I am trying to create a patientcontroller.php in which I want to get data for only one user id from users table so that I can create a dashboard for sngle patient here is my code :
PatientsController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
use Cake\Network\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
class PatientsController extends AppController
{
public function isAuthorized($user)
{
return true;
}
public function index (){
echo $this->Auth->user('id');
$users = TableRegistry::get('Users');
$users->find('all')->where(['id' => 4]);
$this->set('users', $users);
}
I want to get username bio and profile information in my Index.ctp I am trying to pass data using query builder but I am confused so any help

So you have a number of issues with both your approach, techniques and also your code.
Methods
http://book.cakephp.org/3.0/en/controllers.html
If you want to get a single patients record, you should be using the view() method in your controller. So you need to create a new method called view(), in which you can return your record. This method should, in most cases, take an id as the parameter.
Tables
http://book.cakephp.org/3.0/en/orm/table-objects.html
In CakePHP the table which is associated with the controller will be loaded by default. So there is no need to use the TableRegistry to load the table.
Fetching data
http://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html
When you are looking for a single record you have two choices. You can either just use a find, with a first().
$this->Patients->find()
->where(['id' => $id])
->first();
Or you can use get() which will throw an exception if a record isn't found.
$this->Patients->get($id);
Joining associated data
http://book.cakephp.org/3.0/en/orm/associations.html
If you want to join data to your user, then you'll need Tables to manage that data, which can then be contained, using foreign keys in your database. This will change your finds, to include a contain() call.
$this->Patients->find()
->where(['id' => $id])
->contain(['Profiles])
->first();
Outputting data in the view
You can set the result of your find to the view as you have done, and then you can loop through, or output it how you like, using the variable you've set. In your case $user.
Summary
So overall you can see that there is quite a bit you are missing. Hopefully this will help get you on track.

Given that this is basically your third question here asking for exactly the same thing (getting the results of a query) I will point you to these very useful resources that you should read:
The tutorials: http://book.cakephp.org/3.0/en/tutorials-and-examples.html
They basically have everything you need to understand how queries work and how they can be used in the view and forms.
The ORM manual: http://book.cakephp.org/3.0/en/orm.html
You will find plenty examples of getting data and using it after retrieving it.

I Found A solution for this query : Full code for this kind of problem :
PatientsController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
use Cake\Event\Event;
use Cake\Network\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
class PatientsController extends AppController {
public function isAuthorized($user) {
return true;
}
public function index () {
$id = $this->Auth->user('id');
$articles = TableRegistry::get('Users');
$query = $articles->find()
->where(['id' => $id]);
$this->set(compact('query'));
}
}
For Index.ctp
<!-- File: src/Template/Articles/index.ctp (edit links added) -->
<h1>Patient Dashboard</h1>
I am patient
<?php
foreach ($query as $row) {
echo $row->username ,$row->password;
}
?>
This kind of solution is veryhelpful if you are just trying to move from procedural php to oops in cakephp .

Related

Overriding Laravel get and first methods

I need to override above mentioned methods to skip some database records. Using where is not an option since I would have to use it every single time as there are records in database that I do not need most of the time and I am not allowed to delete them from DB. Here is my attempt of doing this:
class SomeTable extends BaseModel {
public static function first() {
$query = static::query();
$data = $query->first();
if($data && $data->type == 'migration_type') return null;
return $data;
}
public static function get() {
$query = static::query();
$data = $query->get();
foreach($data as $key => $item) {
if($item->type == 'migration_type') unset($data[$key]);
}
return $data;
}
}
The problem with this code is that it works only when direct called on model. If I am using some other functions, like where, before get or first methods, it just skips my overridden method.
What would be the right way to do this and should I put this code within model?
My question is not duplicate as in the answer from mentioned question it is said:
all queries made from Models extending your CustomModel will get this new methods
And I need to override those two functions only for specific model, not for each one in application as not all tables have type column. That's the reason why I have written them within model class.
I need to override above mentioned methods to skip some database records.
Consider a global query scope on the model.
https://laravel.com/docs/5.8/eloquent#global-scopes
Global scopes allow you to add constraints to all queries for a given model. Laravel's own soft delete functionality utilizes global scopes to only pull "non-deleted" models from the database. Writing your own global scopes can provide a convenient, easy way to make sure every query for a given model receives certain constraints.
The issue here is that the where() method on the model returns a QueryBuilder instance where get() will return a Collection instance.
You should be able to override collection's default methods by adding a macro in it's place and can be done like so...
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return Str::upper($value);
});
});
Extending the query builder instance is not so easy but a good tutorial exists here and involves overriding the application's default connection class, which is not great when it comes to future upgrades.
Because after calling where you're dealing with the database builder and theses methods inside your model aren't being called .. about the issue you might overcome it by using select instead of first directly so will deal with the builder ..
example:
SomeTable::select('col1','col2')->take(1)->get();
another thing overriding these kind of methods is not a good idea if you're working with other developer on the same project.
good luck

How to query from database when I have different foreign keys?

I am trying to query data from my database and pass the results to a view called events, the problem I have is that one of my queries will always return the same result because in the where condition the $events_id is the same always. Is there a better way to do the querying? A better logic?
This code is from my controller called EventController:
public function index()
{
$firm_id = DB::table('firms')->where('user_id', auth()->id())->value('id');
$events_id = DB::table('events')->where('firm_id', $firm_id)->value('id');
$events = DB::table('events')->where('firm_id', $firm_id)->get()->toArray();
$actual_events = DB::table('actual_events')->where('event_id', $events_id)->get()->toArray();
return view('events',['events' => $events,'actual_events' => $actual_events]);
}
Since the $events_id is the same every time, the $actual_events will only contain the first result.
The image I have uploaded shows the problem, my table's first three columns are fine. Starting from the fourth they contain repeated values:
As I guess, you need something like this:
$event_ids = DB::table('events')->where('firm_id', $firm_id)->pluck('id');
$actual_events = DB::table('actual_events')->whereIn('event_id', $event_ids)->get()->toArray();
or write about your problem in details and I will try to help you.
you just said that your tables have relation together.
in this case it's better you using the eloquent for that,
first you should type the relations in model of each table like this:
class User extends Authenticatable{
public function cities()
{
return $this->hasmany('App\City'); //you should type your relation
}
}
for relations you can use this link: laravel relationships
after that when you compact the $user variable to your view, you can use this syntax for getting the city value relation to this user: $user->cities;.

Counting page views with Laravel

I want to implement page view counter in my app. What I've done so far is using this method :
public function showpost($titleslug) {
$post = Post::where('titleslug','=',$titleslug)->firstOrFail();
$viewed = Session::get('viewed_post', []);
if (!in_array($post->id, $viewed)) {
$post->increment('views');
Session::push('viewed_post', $post->id);
}
return view('posts/show', compact('post', $post));
}
I retrieve the popular posts list like this :
$popular_posts = Post::orderBy('views', 'desc')->take(10)->get();
However, I'd like to know if there are any better ways to do this ? And with my current method, can I get a list of most viewed posts in the past 24 hours ? That's all and thanks!
As quoted in # milo526's comment, you can record all hits to your pages in a unique way instead of an increment. With this you have many possibilities to search for access information, including the listing of the posts sorted by most viewed.
Create a table to save your view records:
Schema::create("posts_views", function(Blueprint $table)
{
$table->engine = "InnoDB";
$table->increments("id");
$table->increments("id_post");
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->string("user_id");
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then, create the corresponding model:
<?php namespace App\Models;
class PostsViews extends \Eloquent {
protected $table = 'posts_views';
public static function createViewLog($post) {
$postsViews= new PostsViews();
$postsViews->id_post = $post->id;
$postsViews->titleslug = $post->titleslug;
$postsViews->url = \Request::url();
$postsViews->session_id = \Request::getSession()->getId();
$postsViews->user_id = \Auth::user()->id;
$postsViews->ip = \Request::getClientIp();
$postsViews->agent = \Request::header('User-Agent');
$postsViews->save();
}
}
Finally, your method:
public function showpost($titleslug)
{
$post = PostsViews::where('titleslug', '=' ,$titleslug)->firstOrFail();
PostsViews::createViewLog($post);
//Rest of method...
}
To search the most viewed posts in the last 24 hours:
$posts = Posts::join("posts_views", "posts_views.id_post", "=", "posts.id")
->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
->groupBy("posts.id")
->orderBy(DB::raw('COUNT(posts.id)', 'desc'))
->get(array(DB::raw('COUNT(posts.id) as total_views'), 'posts.*'));
Note that in PostsViews, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.
You may need to adapt some aspects of this solution to your final code.
2020 Update (2)/ With Eloquent Relationships for Laravel 6
If you don't want to add a package to your application. I have developed the following solution based on "Jean Marcos" and "Learner" contribution to the question and my own research.
All credit goes to "Jean Marcos" and "Learner", I felt like I should do the same as Learner and update the code in a way the would be beneficial to others.
First of all, make sure you have a sessions table in the database. Otherwise, follow the steps in Laravel documentations to do so: HTTP Session
Make sure that the sessions are stored in the table. If not, make sure to change the SESSION_DRIVER variable at the .env set to 'database' not 'file' and do composer dump-autoload afterwards.
After that, you are all set to go. You can start by running the following console command:
php artisan make:model PostView -m
This will generate both the model and migration files.
Inside of the migration file put the following Schema. Be cautious with the columns names. For example, my posts table have the "slug" column title name instead of the "titleslug" which was mentioned in the question.
Schema::create('post_views', function (Blueprint $table) {
$table->increments("id");
$table->unsignedInteger("post_id");
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->unsignedInteger('user_id')->nullable();
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then put the following code inside the PostView model file.
<?php
namespace App;
use App\Post;
use Illuminate\Database\Eloquent\Model;
class PostView extends Model
{
public function postView()
{
return $this->belongsTo(Post::class);
}
public static function createViewLog($post) {
$postViews= new PostView();
$postViews->post_id = $post->id;
$postViews->slug = $post->slug;
$postViews->url = request()->url();
$postViews->session_id = request()->getSession()->getId();
$postViews->user_id = (auth()->check())?auth()->id():null;
$postViews->ip = request()->ip();
$postViews->agent = request()->header('User-Agent');
$postViews->save();
}
}
Now inside the Post model write the following code. This to create the relation between the posts table and the post_views table.
use App\PostView;
public function postView()
{
return $this->hasMany(PostView::class);
}
In the same Post model you should put the following code. If the user is not logged in the the code will test the IP match. Otherwise, it will test both the session ID and the user ID as each user might have multiple sessions.
public function showPost()
{
if(auth()->id()==null){
return $this->postView()
->where('ip', '=', request()->ip())->exists();
}
return $this->postView()
->where(function($postViewsQuery) { $postViewsQuery
->where('session_id', '=', request()->getSession()->getId())
->orWhere('user_id', '=', (auth()->check()));})->exists();
}
You are ready now to run the migration.
php artisan migrate
When the user ask for the post. The following function should be targeted inside the PostController file:
use App\PostView;
public function show(Post $post)
{
//Some bits from the following query ("category", "user") are made for my own application, but I felt like leaving it for inspiration.
$post = Post::with('category', 'user')->withCount('favorites')->find($post->id);
if($post->showPost()){// this will test if the user viwed the post or not
return $post;
}
$post->increment('views');//I have a separate column for views in the post table. This will increment the views column in the posts table.
PostView::createViewLog($post);
return $post;
}
As I have a separate column for views in the post table. To search the most viewed posts in the last 24 hours you write this code in the controller. Remove paginate if you don't have pagination:
public function mostViwedPosts()
{
return Posts::with('user')->where('created_at','>=', now()->subdays(1))->orderBy('views', 'desc')->latest()->paginate(5);
}
I hope this would help/save someones time.
2020 Update
First of all, thanks a lot "Jean Marcos" for his awesome answer. All credit goes to him, I am just pasting a slightly modified answer combining my knowledge of Laravel as well.
Create a table to save your view records and name it with snake_case plural: post_views
Schema::create("post_views", function(Blueprint $table)
{
$table->engine = "InnoDB";//this is basically optional as you are not using foreign key relationship so you could go with MyISAM as well
$table->increments("id");
//please note to use integer NOT increments as "Jean Marcos' answer" because it will throw error "Incorrect table definition; there can be only one auto column and it must be defined as a key" when running migration.
$table->unsignedInteger("post_id");//note that the Laravel way of defining foreign keys is "table-singular-name_id", so it's preferable to use that
$table->string("titleslug");
$table->string("url");
$table->string("session_id");
$table->unsignedInteger('user_id')->nullable();//here note to make it nullable if your page is accessible publically as well not only by logged in users. Also its more appropriate to have "unsignedInteger" type instead of "string" type as mentioned in Jean Marcos' answer because user_id will save same data as id field of users table which in most cases will be an auto incremented id.
$table->string("ip");
$table->string("agent");
$table->timestamps();
});
Then, create the corresponding model. Please note to create "PascalCase" model name and singular form of the table so it should be like: PostView
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class PostView extends Model
{
public static function createViewLog($post) {
$postViews= new PostView();
$postViews->listing_id = $post->id;
$postViews->url = \Request::url();
$postViews->session_id = \Request::getSession()->getId();
$postViews->user_id = (\Auth::check())?\Auth::id():null; //this check will either put the user id or null, no need to use \Auth()->user()->id as we have an inbuild function to get auth id
$postViews->ip = \Request::getClientIp();
$postViews->agent = \Request::header('User-Agent');
$postViews->save();//please note to save it at lease, very important
}
}
Then run the migration to generate this table
php artisan migrate
Finally, your method:
public function showpost($titleslug)
{
$post = PostView::where('titleslug', '=' ,$titleslug)->firstOrFail();
\App\PostView::createViewLog($post);//or add `use App\PostView;` in beginning of the file in order to use only `PostView` here
//Rest of method...
}
To search the most viewed posts in the last 24 hours:
$posts = Posts::join("post_views", "post_views.id_post", "=", "posts.id")
->where("created_at", ">=", date("Y-m-d H:i:s", strtotime('-24 hours', time())))
->groupBy("posts.id")
->orderBy(DB::raw('COUNT(posts.id)'), 'desc')//here its very minute mistake of a paranthesis in Jean Marcos' answer, which results ASC ordering instead of DESC so be careful with this line
->get([DB::raw('COUNT(posts.id) as total_views'), 'posts.*']);
Note that in PostView, you have data that can help further filter your listing, such as the session id, in case you do not want to consider hits from the same session.
You may need to adapt some aspects of this solution to your final code.
So those were few modifications I wanted to point out, also you might want to put an additional column client_internet_ip in which you can store \Request::ip() which can be used as a filter as well if required.
I hope it helps
Eloquent Viewable package can be used for this purpose. It provides more flexible ways to do stuff like this(counting page views).
Note:The Eloquent Viewable package requires PHP 7+ and Laravel 5.5+.
Make Model viewable:
Just add the Viewable trait to the model definition like:
use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\Viewable;
class Post extends Model
{
use Viewable;
// ...
}
Then in the controller:
public function show(Post $post)
{
$post->addView();
return view('blog.post', compact('post'));
}
After that you can do stuff like this:(see package installation guide for more details)
// Get the total number of views
$post->getViews();
// Get the total number of views since the given date
$post->getViews(Period::since(Carbon::parse('2014-02-23 00:00:00')));
// Get the total number of views between the given date range
$post->getViews(Period::create(Carbon::parse('2014-00-00 00:00:00'), Carbon::parse('2016-00-00 00:00:00')));
// Get the total number of views in the past 6 weeks (from today)
$post->getViews(Period::pastWeeks(6));
// Get the total number of views in the past 2 hours (from now)
$post->getViews(Period::subHours(2));
// Store a new view in the database
$post->addView();
Implements same kind of idea as in the accepted answer, but provides more features and flexibilities.
First of all thanks to user33192 for sharing the eloquent viewable. Just want to make it clearer for others after looking at the docs. Look at the docs to install the package.
Do this in your Post Model:
use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\InteractsWithViews;
use CyrildeWit\EloquentViewable\Viewable;
class Post extends Model implements Viewable
{
use InteractsWithViews;
// ...
}
In your posts controller, use the record method to save a view;
public function show($slug)
{
$post = Post::where('slug',$slug)->first();
views($post)->record();
return view('posts.show',compact('post'));
}
In your views you can return the views (mine is posts.show) as you want. Check the document for more. I will just the total views of a post.
<button class="btn btn-primary">
{{ views($post)->count() }} <i class="fa fa-eye"></i>
</button>

Laravel 4 Use the same Model and Controller for 12 lists for selects

I have to meke models, controllers and views for 12 tables. They have all the same structure id, name, order.
I was thinking and maybe using:
Controller
index($model)
$model::all()
return View::make(all_tables,compact('model'))
edit($model,$id)... and so on.
But and don't know if there's a way for using only one model.
Did anybody do anything like this?
Any idea?
Thanks
Although each model has the same table structure, what you're trying to achieve would not be advisable as you'd lose a lot of the fluent capabilities of Laravel's Eloquent ORM.
Regarding the controller, this would work:
<?php
namespace App\Http\Controllers;
use App\Http\Requests;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class GenericModelController extends Controller
{
public function loadModelById($model, $id)
{
$instance = \App::make('App\\' . ucfirst($model));
return $instance->find($id);
}
}
You'll need the following route:
Route::get('show/{model}/{id}', 'GenericModelController#loadModelById');
Example, to load a user with an id of 1:
http://www.yourdomain.com/show/user/1
Edit: I just saw that you're using Laravel 4, so the syntax for defining a route will be a little different I believe but the general concept will still work. Testing in Laravel 5 and works perfectly.
You should get get some idea from here.Please use the link below.
https://scotch.io/tutorials/a-guide-to-using-eloquent-orm-in-laravel
// app/models/Bear.php
class Bear extends Eloquent {
// MASS ASSIGNMENT -------------------------------------------------------
// define which attributes are mass assignable (for security)
// we only want these 3 attributes able to be filled
protected $fillable = array('name', 'type', 'danger_level');
// DEFINE RELATIONSHIPS --------------------------------------------------
// each bear HAS one fish to eat
public function fish() {
return $this->hasOne('Fish'); // this matches the Eloquent model
}
// each bear climbs many trees
public function trees() {
return $this->hasMany('Tree');
}
// each bear BELONGS to many picnic
// define our pivot table also
public function picnics() {
return $this->belongsToMany('Picnic', 'bears_picnics', 'bear_id', 'picnic_id');
}
}
I find a simple way.
Only one model, one controller and one view(index,edit, etc) too.
A single table with
id, name of list, value (name to appears in the list)
Yo pass can pass to de view all the values per list, and for any list in the table you can create de select if it's no empty.

Laravel 4: How to add more data to Auth::user() without extra queries?

I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:
A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.
We added a method account() to our User model:
/**
* Get the account the user is currently logged in to
*/
public function account()
{
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
return Account::where('subdomain', $subdomain)->first();
}
The problem is that there is always an extra query when we now use something like this in our view or controller:
Auth::user()->account()->accountname
When we want to get "Products" related to the account, we could use:
$products = Product::where('account_id', Auth::user()->account()->id)->get();
And yet again an extra query...
Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..
What's the best solution for this?
Thanks in advance
Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.
Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.
You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships
It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.
Using Eloquent relationships you can write in your User model:
<?php
public function account()
{
// I assume here 'username' is the local key for your User model
return $this->hasOne('Account', 'subdomain', 'username');
}
public function products()
{
// You should really have a user_id in your User Model
// so that you will not have to use information from the
// user's account
return $this->hasMany('Product', 'account_id', 'user_id');
}
You should define the belongsTo in your Account model and Product model.
With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading
You will be able to use something like
$users = User::with('account', 'products')->get();
To get all users with their account and products.
I think this is a good example for the purpose of Repositories.
You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.
For instance:
<?php
class ProductRepository
{
protected $accountId;
public function __construct($accountId)
{
$this->accountId = $accountId;
}
public function all()
{
return Product::where('account_id', $this->accountId)->get();
}
}
//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
return new ProductRepository(Auth::user()->account()->id);
});
// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();
You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.
Scopes could also be interesting in this scenario:
// app/models/Product.php
public function scopeCurrentAccount($query)
{
return $query->where('account_id', Auth::user()->account()->id);
}
Now you could simply call
$products = Product::currentAccount()->get();

Resources