BurgerPedia: A Complete Laravel 5 and AngularJS Tutorial with Bootstrap to Make it Pretty – Part 6

in AJAX/HTML/CSS/jQuery/Javascript/Laravel/PHP/Tutorials & Samples/Web Development

Part 6 – Creating The Controllers

Now that we’ve seen the basic of setting up a route. We can start getting into controllers. So far, we have a route inside our route file with a specific logic. What we will do is take the logic out of the route file and put it inside a controller. Adding the logic inside the route is a quick and easy way to make sure that your logic makes sense. But once it makes sense, it is better to move that logic inside a controller. Nothing stops you from leaving the entire logic inside the route files but I personally think that it works out much better to have a controller file that defines the route logic.

We will create a couple of controllers called HamburgerController and HamburgerDescriptionController using artisan:

	php artisan make:controller HamburgerController
	Php artisan make:controller HamburgerDescriptionController

If you go inside the app\Http\Controllers folder, you will see that both files got created. If you open any of these files, you will see that these controllers extend the basic controller. We can now setup the CRUD (Create, Update, Destroy) operators inside both controllers.

Now that we have the controller setup, we can go back to the route file and replace the closure with the proper controller function. So instead of having to define every controller operation in the route, we will use the resource so that larvael can assume that the basic REST hierarchy is followed in our controller. So for example, if there is a GET request, it will assume that we should use the index() function since we are most likely performing a listing operation. But if we have a GET request with an ID, then it will use the show() function. We will declare a route resource, in this case it’s a hamburgers resource that is associated to HamburgerController. So to do this, we will use a route resource by typing the following:

	Route::resource('hamburgers','HamburgerController');</p>

So, lets take a moment to look at the various HTTP methods and how the match to Laravel methods. The following is a table showing how the methods match for our hamburgers controller:

Verb URI Action Route Name
GET /hamburgers index hamburgers.index
GET /hamburgers/create create hamburgers.create
POST /hamburgers store hamburgers.store
GET /hamburgers/{hamburger} show hamburgers.show
GET /hamburgers/{hamburger}/edit edit hamburgers.edit
PUT/PATCH /hamburgers/{hamburger} update hamburgers.update
DELETE /hamburgers/{hamburger} destroy hamburgers.destroy

The route resource creates the binding between the path and the controller. Now all the REST hierarchy such as index(), show(), update(), destroy() will be associated to the controller methods. For the sake of this tutorial, not all REST controls will exist for hamburgers. So, I can add an additional parameter, which is an array. Within this array, I can specify the controls that I support using the ‘only’ keyword. For the sake of this tutorial, I only want the index(), store() and show() methods to be available to users.

	Route::resource('hamburgers','HamburgerController',['only'=>['index','store','show']]);

Assuming you know how REST works:

  • Index() would be a GET request without an ID
  • Store() would be a POST request without an ID
  • Show() would be a GET request with an ID
  • Update() would be a PUT request with the resource ID

Because Laravel fully supports REST, it is automagically taking care of this for you. We can now go ahead and define these three functions in the hamburger controller and add the logic to make it all work.

We can now do the same thing for the hamburger descriptions as well. The only constraint we have to remember is that a description always belongs to a hamburger. So we do not want users to add independent descriptions. A description must always belong to a hamburger. So to that we must do the following:

	Route::resource('hamburgers.descriptions','HamburgerDescriptionController',['only'=>['index','store','show','update','destroy']]);

The dot between hamburgers and descriptions implicitly tells laravel that the description belongs to the hamburger. In this case, we only support the index(), store(), show(), update() and destroy() methods. My full routes/api.php now looks like so:

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::resource('hamburgers','HamburgerController',['only'=>['index','store','show','update']]);
Route::resource('hamburgers.descriptions','HamburgerDescriptionController',['only'=>['index','store','show','update','destroy']]);

Since this is a simple application with not many routes, it is easy to follow. But imagine you had a complicated application with many routes. Then it may get complicated to keep track of all the routes that are currently setup. Luckily Laravel does a good job of maintaining the routes list through artisan. You can simply type the following command to list all of the routes:

	php artisan route:list

This will then list all of the actions, URI and their middleware:

routes-list

Now let’s go ahead and write the methods. Inside the hamburger controller, we need to specify which model we will be using. This is done by adding the following at the top of the file:

	use App\Hamburger

Now we have direct access to the Hamburger model.

Listing our data

Now we can write the index() function for our hamburger which looks like so:

	public function index(){
		return Hamburger::all();
	}

As you can see, its an exact copy of what we had in the route file but we’ve omitted the namespace because we have explicitly stated that we are using the App namesame where the hamburger model resides.

One thing to note is that the all() method is a bit dangerous, especially when you have a lot of records. Imagine that your application had 1 million records. Then calling Hamburger::all() will return one million records, which could So, we will use the paginate method to add the built-in pagination functionalities. To do so, just have to change all() to paginate(). The file now looks like so:

	public function index(){
		return Hamburger::paginate();
	}

You will now notice that the pagination information is added in the return JSON string. So we are now given the total, how many there are per page, and other relevant pagination information. Here is a sample output:

hamburgers-paginated

In the case of the description controler, our index() function must only list descriptions that belong to a given hamburger. Therefore we need to pass the hamburger id to our index() function. To do so we need to build a scope. Scopes are part of eloquent’s query modifiers within the model. Scopes allow you to apply conditions to queries, which gives you power to retrieve and present filtered data in any way imaginable. Scopes are defined at the model level. So we will go ahead and build a scope inside the description model that is called scopeOfHamburger(). This scope will limit the returned descriptions to only those that belong to a given hamburger ID. To do so, add the following code inside the description model:

	public function scopeOfHamburger($query, $hamburgerId){
		return $query->where('hamburger_id', $hamburgerId);
	}

The first parameter is the query and second is the ID of the hamburger that we are referring to. A scope must always return a query. So now we can modify our index() for the description to include the scope that we have just defined so that it becomes:

	public function index($hamburgerId){
		return Description::ofHamburger($hamburgerId)->paginate();
	}

Now we will get a list of all descriptions that belong to the given hamburger ID. This can be tested using postman or your browser by going to the following link: http://localhost/burgerpedia/public/api/hamburgers/1/descriptions

(Remember that in my case, I am using xampp so this is the path to my application. Your path will probably be different)

descriptions-index

So scopes are an excellent way to make sure the controller does not get bloated with additional logic. This allows us to stick to the “fat model, skinny controller” principle that I am a big fan of.

Inserting data

Inserting data is not much harder than listing data. All that we need to do is use the request parameter that is passed to our store() method. We are going to use the data inside our request object to create a new hamburger. The expected code looks like so:

public function store(Request $request){
		
	$hamburger = Hamburger::create([
		'name' => $request->input('name'),
		'author' => $request->input('author'),
		'overview' => $request->input('overview')
	]);
		
	return $hamburger;
}

There is a create() method that eloquent provides to create a new record in the database and it accepts a list of attributes. The attributes that we care about are all the fillable fields that we defined in the routes/api.php file. This name attribute must be equal to the name parameter that we are expecting in our request. So as long as we get a request that includes all the required fillable fields, it will create a hamburger in our database.

Notice that I did not do any additional validation because the validation will be handled later on.

So, using Postman, or any other similar plugin, I can send a post request with all 3 parameters to the URL http://localhost/burgerpedia/public/api/hamburgers and a new hamburger will be added to our database of hamburgers with the provided name, author and overview.

Finally, we want to have the ability to list information about a specific hamburger. This is done through the show() method which is shown below:

public function show($id){
	$hamburger = Hamburger::findOrFail($id);
		
	return $hamburger;
}

Keep in mind that you don’t want to update a hamburger that does not exist. To prevent this, we will use another eloquent method called findOrFail() which will look for a specific hamburger. If it is found, then it will return the hamburger, otherwise, it will return an error.

Now, lets add the ability to add descriptions to our hamburgers. In the case of the hamburger descriptions, our store method must also contain the hamburgerId along with the request. And more importantly, we want to make sure that the hamburger that we are adding a description to actually exists. This involves once again using the findOrFail() method. The expected code looks like so:

public function store(Request $request, $hamburgerId){
	
	$hamburger = Hamburger::findOrFail($hamburgerId);
	$hamburger->descriptions()->save(new Description([
		'title' => $request->input('title'),
		'description' => $request->input('description'),
		'author' => $request->input('author')
	]));
		
	return $hamburger->descriptions;
}

Updating Data

For this simple example, we do not want to give visitors the ability to update information about a hamburger. But we will let them update a hamburger description if they are the author of the original description. This update method is pretty straightforward. We first call the findOrFail() method to make sure that our description already exists. If it exists, we update the the data value that we received from the request. The full method looks like so:

public function update(Request $request, $hamburgerId, $descriptionId){
	
	$description = Description::ofHamburger($hamburgerId)->findOrFail($descriptionId);
	
	if($request->input('author') == $description['author']){
		$description->update([
			'title' => $request->input('title'),
			'description' => $request->input('description'),
			'author' => $request->input('author')
		]);
		return $description;
	}else{
		// Unprocessable entity
		return response()->json(['name' => 'Failure! You are not the author of this description'], 422);

	}
}

Deleting Data

Since we have already setup hamburger descriptions to be use soft-delete, we will need to create a delete method for our hamburger descriptions. THis is done through the destroy() method which is defined below.

public function destroy($hamburgerId,$descriptionId)
{
	$description = Description::ofHamburger($hamburgerId)->findOrFail($descriptionId);
 
	$description->delete();
 
	return $description;
}

Note that we do not allow users to delete hamburgers in this tutorial.

So, here is the full content of the HamburgerController file:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;

use App\Hamburger;

class HamburgerController extends Controller
{
	
	public function index(){
		return Hamburger::paginate();
	}
	
	public function show($id){
		$hamburger = Hamburger::findOrFail($id);
			
		return $hamburger;
	}
	
	public function store(Request $request){
		
		$hamburger = Hamburger::create([
			'name' => $request->input('name'),
			'author' => $request->input('author'),
			'overview' => $request->input('overview')
		]);
			
		return $hamburger;
	}
	
}

And here is the full content of the HamburgerDescriptionControllerController file:

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;

use App\Description;
use App\Hamburger;

class HamburgerDescriptionController extends Controller
{
	public function index($hamburgerId){
		return Description::ofHamburger($hamburgerId)->paginate();
	}
	
	public function show($hamburgerId,$descriptionId){
		$description = Description::ofHamburger($hamburgerId)->findOrFail($descriptionId);
			
		return $description;
	}
	
	public function store(Request $request, $hamburgerId){

		$hamburger = Hamburger::findOrFail($hamburgerId);
		$hamburger->descriptions()->save(new Description([
			'title' => $request->input('title'),
			'description' => $request->input('description'),
			'author' => $request->input('author')
		]));
			
		return $hamburger->descriptions;
	}
	
	public function update(Request $request, $hamburgerId, $descriptionId){
		
		$description = Description::ofHamburger($hamburgerId)->findOrFail($descriptionId);
		
		if($request->input('author') == $description['author']){
			$description->update([
				'title' => $request->input('title'),
				'description' => $request->input('description'),
				'author' => $request->input('author')
			]);
			return $description;
		}else{
			// Unprocessable entity
			return response()->json(['name' => 'Failure! You are not the author of this description'], 422);

		}
	}
	
	public function destroy($hamburgerId,$descriptionId)
	{
		$description = Description::ofHamburger($hamburgerId)->findOrFail($descriptionId);
	 
		$description->delete();
	 
		return $description;
	}
	
}

Previous Step: Setting up the Routes
Next Step: Basic Validations

Tutorial Contents
Tutorial Resources

Mifty Yusuf is a Montreal-based software developer who enjoys playing with new web technologies as well as comic books and illustrations. He beleives that, no matter what the question is, the answer is always Batman!

Leave a Reply

Your email address will not be published.

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Latest from AJAX

Go to Top