I am currently new to laravel and I am trying to wrap my head around code structure with laravel. The reason for learning laravel is to grow my knowledge on creating good code in addition to creating an easily to manage application. For this reason, It is important to me that I am building my application to an accepted standard.
I have read several articles but am still not exactly sure where to organize things.
As of now, my application structure looks as so:
app/
Console
Exceptions
Http
Controllers
Auth
Message
Settings
Middleware
Providers
Traits
Message
Settings
One of my controller looks like something like this:
<?php
namespace App\Http\Controllers\Message;
use DB;
use Auth;
use Request;
use App\Http\Controllers\Controller;
class TypeController extends Controller
{
public function __construct () {
$this->middleware('auth');
$this->middleware('access');
}
public function type () {
$this->adjustTypingStatus(1);
}
public function untype () {
$this->adjustTypingStatus(0);
}
protected function adjustTypingStatus ($level) {
DB::table('user_lobby_info')
->where('userid', Auth::User()->id)
->where('lobby', Request::get('lobbyid'))
->update([ 'typing' => $level ]);
}
}
?>
The Question
What is a better way to separate controllers into smaller, more manageable pieces? Should I put the database logic into the model and just call the model's method?
This is how I break down controller logic and use the Model for small to medium size projects.
1. Create your table and model
This command will create your Model and the --migration will create a migration file that references the BluePrint class that you can use to create your Model's table.
php artisan make:model UserLobbyInfo --migration
You seem to have a database already created, so you may want to remove the --migration, unless you want to use it to create schemas using BluePrint. I personally like using the migrations. Your Model will be created directly under the App folder in Laravel 5.
2. Modify your Model file
You find your Model file in the App folder. In your model you should add the fields you'll insert or update (mass fillable items) and the name of your table if it doesn't follow the Laravel convention (Laravel assumes that the camel casing indicates different words and that your table ends with an 's', so it thinks your table will be user_lobby_infos, in your case, your table name is user_lobby_info). This is how I'd update it based in your data in your query above:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserLobbyInfo extends Model
{
// Define mass fillable items
protected $fillable = array('userid','lobby','typing');
//Define table
protected $table = 'user_lobby_info';
}
How you can Use your Model
This model now has all the methods provided to it from it's extended Illuminate\Database\Eloquent\Model class, so you can do the following and more:
//To query all content:
$index = UserLobbyInfo::all();
//To query specific content:
$userLobby = UserLobbyInfo::where('id', '=', 1)->first();
//Save things:
$userLobbyInfo = UserLobbyInfo::where('id', '=', 1)->first();
$userLobbyInfo->lobby = Request::get('lobbyid')
$userLobbyInfo->save();
//Using the model for your query above this is how you can send an update:
UserLobbyInfo::where('userid', '=', Auth::User()->id)
->where('lobby', '=', Request::get('lobbyid'))
->update([ 'typing' => $level ]);
3. Creating Controller pre-canned with CRUD related methods
This command will create a controller with all the methods you'd typically use in a CRUD app (Index, show, create, save, edit, update, delete)
php artisan make:controller UserLobbyController --resource
in each of these functions you'd add the respective model with the method you need.
4. Add all the routes traditionally used in a CRUD App and linked to the --resource methods
If you use the --resource you will be able to use a resource function that will provide you with all the routes required for those respective resources.
Route::resource('userlobby', 'UserLobbyController');
that one line in your route file will create the following routes typical in a CRUD app run "php artisan route:list |grep userlobby" and youll see these routes:
| | GET|HEAD | userlobby | userlobby.index | App\Http\Controllers\UserLobbyController#index | web |
| | POST | userlobby | userlobby.store | App\Http\Controllers\UserLobbyController#store | web |
| | GET|HEAD | userlobby/create | userlobby.create | App\Http\Controllers\UserLobbyController#create | web |
| | GET|HEAD | userlobby/{userlobby} | userlobby.show | App\Http\Controllers\UserLobbyController#show | web |
| | PUT|PATCH | userlobby/{userlobby} | userlobby.update | App\Http\Controllers\UserLobbyController#update | web |
| | DELETE | userlobby/{userlobby} | userlobby.destroy | App\Http\Controllers\UserLobbyController#destroy | web |
| | GET|HEAD | userlobby/{userlobby}/edit | userlobby.edit | App\Http\Controllers\UserLobbyController#edit | web |
5. Condense your Controller to CRUD methods
I will just an edit and update method below as this can get quite lengthy. Hopefully this gives you an idea on how to breakdown a controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\UserLobbyInfo; // Add this to access your new model
use App\Http\Requests;
class UserLobbyController extends Controller
{
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function edit($id)
{
$updateLobby = UserLobbyInfo::where('id', '=', $id)->first(); //This queries the table specifically for the id, just for demo purposes.
return view('lobbies.edit', compact('updateLobby')); //This will send the above defined array to your view to pre-populate.
}
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param int $id
* #return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
$userLobby = UserLobbyInfo::where('userid', '=', Auth::User()->id)
->where('lobby', '=', $request->lobbyid)->first();
//Grab the UserLobby row you want to update.
$updateLobby->typing = $level; //update the typing value
$updateLobby->save(); // save it.
}
For more complex applications, I typically will migrate the heavier controller logic out to a class, and reference the class in the controller using it. I also only use DB:: class when I'm writing a complex query with multiple table joins (especially a join with multiple where clauses required in that join).
Hopefully that helps highlight how to properly use the Model in Laravel.
A lot of this info is available in the laravel documentation. I also like this cheat sheet: laravel cheat sheet
Any more questions, or if I didn't fully answer your question, just let me know.
Use Eloquent instead of raw SQL queries or Query Builder queries. Put all data related logic into Model classes:
public function getApprovedUsersWithPosts()
{
return $this->where('approved', true)->with('posts')->get();
}
Your controllers should be really small. Never put queries or other logic into controllers. Also, use DI instead of facades:
protected $user;
public function __construct(User $user)
{
$this->user = $user;
}
public function index()
{
return view('users.approved', [
'users' => $this->user->getApprovedUsers()
]);
}
Related
I'm currently doing the dashboard in our project and can't progress because of this error. Call to undefined method App\Charts\SampleChart::labels(). I suspect that I missed some installation and configurations but I've done it several times now.
SampleChart.php
<?php
declare(strict_types = 1);
namespace App\Charts;
use Chartisan\PHP\Chartisan;
use ConsoleTVs\Charts\BaseChart;
use Illuminate\Http\Request;
class SampleChart extends BaseChart
{
/**
* Handles the HTTP request for the given chart.
* It must always return an instance of Chartisan
* and never a string or an array.
*/
public function handler(Request $request): Chartisan
{
return Chartisan::build()
->labels(['First', 'Second', 'Third'])
->dataset('Sample', [1, 2, 3])
->dataset('Sample 2', [3, 2, 1]);
}
}
dashboardcontroller
<?php
namespace App\Http\Controllers;
use App\Charts\SampleChart;
use Illuminate\Http\Request;
use App\Models\ratings;
class DashboardController extends Controller
{
public function dashboard() {
$rating = ratings::all();
$chart = new SampleChart;
$chart->labels(['One', 'Two', 'Three']);
$chart->dataset('My Dataset 1', 'line', [1,2,4]);
return view('user/admin/dashboard', compact('rating'));
}
}
charts.php
<?php
declare(strict_types=1);
return [
/*
|--------------------------------------------------------------------------
| Global Route Prefix
|--------------------------------------------------------------------------
|
| This option allows to modify the prefix used by all the chart routes.
| It will be applied to each and every chart created by the library. This
| option comes with the default value of: 'api/chart'. You can still define
| a specific route prefix to each individual chart that will be applied after this.
|
*/
'global_route_prefix' => 'api/chart',
/*
|--------------------------------------------------------------------------
| Global Middlewares.
|--------------------------------------------------------------------------
|
| This option allows to apply a list of middlewares to each and every
| chart created. This is commonly used if all your charts share some
| logic. For example, you might have all your charts under authentication
| middleware. If that's the case, applying a global middleware is a good
| choice rather than applying it individually to each chart.
|
*/
'global_middlewares' => ['web'],
/*
|--------------------------------------------------------------------------
| Global Route Name Prefix
|--------------------------------------------------------------------------
|
| This option allows to modify the prefix used by all the chart route names.
| This is mostly used if there's the need to modify the route names that are
| binded to the charts.
|
*/
'global_route_name_prefix' => 'charts',
];
i followed this site just like what creators on youtube tells: https://charts.erik.cat/guide/installation.html
what could be the problem? please help
It looks like you are trying to call ->labels() on an instance of SampleChart based on the principle of multi-inheritance in php. The problem with that here is that ->labels() is a method of Chartisan class not SampleChart or BaseChart which it extends, hence the error you get (basically just saying hey, I cannot find labels()).
See this answer for an explanation of multi-level inheritance in PHP.
More info: https://www.php.net/manual/en/language.oop5.inheritance.php
I'm curious what your use case for doing this in your controller is though. If it is for rendering, the docs you provided has this: https://charts.erik.cat/guide/render_charts.html
I have been using RESTful controllers in my Laravel project. By including:
Route::controller('things', 'ThingController')
in my routes.php, I can define functions in the ThingController like:
public function getDisplay($id) {
$thing = Thing::find($id)
...
}
so that GETting the URL "...things/display/1" would automatically be directed to the controller function. This seems pretty handy and has been working great for me so far.
I noticed many of my controller functions start with getting a model by id from the url, and I thought it would be nice to be able to use route model binding to do this for me instead. So I updated my routes.php to
Route::model('thing', 'Thing');
Route::controller('things', 'ThingController')
and changed the ThingController functions to
public function getDisplay($thing) {
...
}
I assumed this would magically work the way I wanted it to (like everything else I've tried so far in Laravel has) but unfortunately I get "Trying to get property of non-object" when I attempt to use $thing in the function. Is this something that should be able to work and I have just done it wrong, or can route model binding only work with routes explicitly named in routes.php?
If you don't mind with URI path, method name and just work only show, edit and update method, you can use Resource Controller to generate URI string which can define model binding.
In routes.php change to
Route::model('things', 'Thing');
Route::resource('things', 'ThingController');
You can use php artisan routes command to see all URIs
$ artisan routes | grep ThingController
GET|HEAD things | things.index | ThingController#index
GET|HEAD things/create | things.create | ThingController#create
POST things | things.store | ThingController#store
GET|HEAD things/{things} | things.show | ThingController#show
GET|HEAD things/{things}/edit | things.edit | ThingController#edit
PUT things/{things} | things.update | ThingController#update
PATCH things/{things} | | ThingController#update
After that you can threat parameter as Thing object without explicitly name route.
/**
* Display the specified thing.
*
* #param Thing $thing
* #return mixed
*/
public function show(Thing $thing)
{
return $thing->toJson();
}
If you want to access ThingController#show, pass your model ID and Laravel will retrieve it automatically.
http://example.com/things/1
{"id":1,"type":"Yo!"}
You can use Route:resource and still provide other methods. Place the route you need just before that particular Route::resource line.
Eg:
Route::model('things', 'Thing');
Route::get('things/{things}/owner', 'ThingController#getOwner');
Route::resource('things', 'ThingController');
Then create the corresponding method in your controller.
public function getOwner($things) {
return Response::json($things->owner()->get());
}
Here is the official documentation from the Laravel 4.2 docs:
Source: http://laravel.com/docs/controllers#resource-controllers
Adding Additional Routes To Resource Controllers
If it becomes necessary for you to add additional routes to a resource controller beyond the default resource routes, you should define those routes before your call to Route::resource:
Route::get('photos/popular');
Route::resource('photos', 'PhotoController');
I have two controllers and I try pass id of variable form method one controller to method to second controller and I got an error like this >MethodNotAllowedHttpException . I will add that my url after the action looks like this >http://localhost/comment?12 . How is the best way of solving this problem ?
You are most probably getting MethodNotAllowedException, because you are opening a route that is defined as a POST route via GET or the other way around.
To avoid that you can use php artisan route:list and get a list of all defined routes and see how you should "access" them:
+--------+-----------+----------------------------------------------------+------------------------+------------------------------------------------------------------------+--------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+----------------------------------------------------+------------------------+------------------------------------------------------------------------+--------------+
| | GET|HEAD | /a/show/{id} | | App\Http\Controllers\AController#show | web |
| | GET|HEAD | /b/show/{id} | | App\Http\Controllers\BController#show | web |
So let's say you have 2 controllers: AController and BController. Each of the controllers have a show() method declared in them.
class AController extends Controller {
//... other AController related code
public function show($id) {
dd($id);
}
}
class BController extends Controller {
//... other BController related code
public function show($id) {
dd($id);
}
}
Then you can define your routes like this:
Route::get('/a/show/{id}', 'AController#show');
Links like: example.com/a/show/10 will "load" AController's show() method. All we have in our AController::show() method's body is dump and die on $id, we will get 10 printed if we visit that link.
We can replace that dd($id); with:
redirect()->action('BController#show', ['id' => $id]);
And define another route:
Route::get('/b/show/{id}', 'BController#show');
This way if we open the previous link: example.com/a/show/10, we will be redirected to: example.com/b/show/10 and BController::show() method will be executed and it prints the variable using dump and die.
Key points:
Route Parameters
Controller Parameters
redirect()-ing
I am trying to display information submitted from a form to create a message board. I am using php in the laravel framework. I am using a form, record, and repository. Whenever I try to display the content I receive the following error Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException.
Here is the controller:
/**
* Display a listing of the resource.
* GET /messages
*
* #return Response
*/
public function create()
{
return View::make('comments.create');
}
public function show($comment)
{
$message_id = $this->messageRepository->find($comment);
return View::make('comments.show')->with('comment', $message_id);
}
/**
* Store a newly created resource in storage.
* POST /messaages
*
* #return Response
*/
public function store()
{
$data = Input::all() ;
$this->messageForm->validate($data);
$messageRecord = new MessageRecord;
$messageRecord->comment = $data['comment'];
Return "Comment created";
}
}
Here is the view causing the trouble:
<p>$comment</p>
I have not been using a route but I threw this together:
Route::resource('/message', 'MessageController');
[Edit:]
If your store() is already used by another form and you still have another form to send POST data then, you should declare another routes for that. Like
Route::post('message/display', ['as' => 'message.display', 'uses' => 'MessageController#display');
Sorry for my previous answer, show() is for GET request and since you are using form you probably you don't need it unless you decide to send data through url. I'm extremely sorry.
[Edit ends]
You are getting that error because you are not allowed to use display() in resourceful routing. Instead, you must use store(). Since you are using laravel resourceful routing, you should follow the convention of laravel. In your command line, run
php artisan routes
You will get something like this,
GET|HEAD message | message.index | MessageController#index
GET|HEAD message/create | message.create | MessageController#create
POST message | message.store | MessageController#store
GET|HEAD message/{message} | message.show | MessageController#show
GET|HEAD message/{message}/edit | message.edit | MessageController#edit
PUT message/{message} | message.update | MessageController#update
PATCH message/{message} | | MessageController#update
DELETE message/{message} | message.destroy | MessageController#destroy
These are the methods, their route names and their corresponding HTTP request that you should use.
Check Laravel documentation for resourceful controller
For quick look,
index : Display a listing of the resource,
create : Show the form for creating a new resource,
store : Store a newly created resource in storage,
show : Display the specified resource,
edit : Show the form for editing the specified resource,
update : Update the specified resource in storage,
destroy : Remove the specified resource from storage
It seems you use incorrect verb for route displaying this route. For display you should probably use Route::get and you probably use Route::post. If it's not the issue you edit your question and put there routes.php as you were asked in comment.
I have been using RESTful controllers in my Laravel project. By including:
Route::controller('things', 'ThingController')
in my routes.php, I can define functions in the ThingController like:
public function getDisplay($id) {
$thing = Thing::find($id)
...
}
so that GETting the URL "...things/display/1" would automatically be directed to the controller function. This seems pretty handy and has been working great for me so far.
I noticed many of my controller functions start with getting a model by id from the url, and I thought it would be nice to be able to use route model binding to do this for me instead. So I updated my routes.php to
Route::model('thing', 'Thing');
Route::controller('things', 'ThingController')
and changed the ThingController functions to
public function getDisplay($thing) {
...
}
I assumed this would magically work the way I wanted it to (like everything else I've tried so far in Laravel has) but unfortunately I get "Trying to get property of non-object" when I attempt to use $thing in the function. Is this something that should be able to work and I have just done it wrong, or can route model binding only work with routes explicitly named in routes.php?
If you don't mind with URI path, method name and just work only show, edit and update method, you can use Resource Controller to generate URI string which can define model binding.
In routes.php change to
Route::model('things', 'Thing');
Route::resource('things', 'ThingController');
You can use php artisan routes command to see all URIs
$ artisan routes | grep ThingController
GET|HEAD things | things.index | ThingController#index
GET|HEAD things/create | things.create | ThingController#create
POST things | things.store | ThingController#store
GET|HEAD things/{things} | things.show | ThingController#show
GET|HEAD things/{things}/edit | things.edit | ThingController#edit
PUT things/{things} | things.update | ThingController#update
PATCH things/{things} | | ThingController#update
After that you can threat parameter as Thing object without explicitly name route.
/**
* Display the specified thing.
*
* #param Thing $thing
* #return mixed
*/
public function show(Thing $thing)
{
return $thing->toJson();
}
If you want to access ThingController#show, pass your model ID and Laravel will retrieve it automatically.
http://example.com/things/1
{"id":1,"type":"Yo!"}
You can use Route:resource and still provide other methods. Place the route you need just before that particular Route::resource line.
Eg:
Route::model('things', 'Thing');
Route::get('things/{things}/owner', 'ThingController#getOwner');
Route::resource('things', 'ThingController');
Then create the corresponding method in your controller.
public function getOwner($things) {
return Response::json($things->owner()->get());
}
Here is the official documentation from the Laravel 4.2 docs:
Source: http://laravel.com/docs/controllers#resource-controllers
Adding Additional Routes To Resource Controllers
If it becomes necessary for you to add additional routes to a resource controller beyond the default resource routes, you should define those routes before your call to Route::resource:
Route::get('photos/popular');
Route::resource('photos', 'PhotoController');