Complete code for DRY resource controllers in Laravel

Below is the complete code which accompanies DRY resource controllers in Laravel.

GenericResourceController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Session;

class GenericResourceController extends Controller
{
	protected $request;
	protected $object;
	protected $id = false;
	
	// child class properties
	protected $model = ''; // e.g. 'App\Product'
	protected $fields = [];
	protected $intermediaries = []; // e.g. ['tags' => ['save' => false, 'destroy' => true]]
	protected $objectLang = ''; // e.g. 'product'
	protected $redirectAfter = [
		'store' => '', // e.g. 'products.show'
		'update' => '',
		'destroy' => '',
	];

	public function store(Request $request){
		$this->request = $request;
		$this->processFormSubmission();
		return $this->redirect('store');
	}
	
	public function update(Request $request, $id){
		$this->request = $request;
		$this->id = $id;
		$this->processFormSubmission();
		return $this->redirect('update');
	}

	public function destroy($id){
		$this->id = $id;
		$this->destroyObject();
		return $this->redirect('destroy');
	}
	
	/**
	 * Handle form submissions for new and existing records
	 */
	protected function processFormSubmission(){
		$this->setObject();
		$this->validateRequest();
		$this->setObjectData();
		$this->saveObject();
		$this->handleIntermediaries('save');
		$this->setSuccessMessage('save');
	}
	
	/**
	 * Handle destroy actions
	 */
	protected function destroyObject(){
		$this->setObject();
		$this->handleIntermediaries('destroy');
		$this->deleteObject();
		$this->setSuccessMessage('destroy');
	}
	
	/**
	 * Set the object property.
	 * If there's no id, create a new object of the appropriate type,
	 * otherwise instantiate the object to correspond to the object id.
	 */
	protected function setObject(){
		$model = $this->model;
		if($this->id === false){
			$this->object = new $model;
		} else {
			$this->object = $model::find($this->id);
		}
	}
	
	/**
	 * Cycle through the form fields and validate them.
	 */
	protected function validateRequest(){
		if(
			isset($this->fields['slug']) &&
			$this->request->input('slug') != $this->object->slug
		){
			$this->fields['slug'] = 'required|alpha_dash|min:5|max:255|unique:' . $this->object->getTable() . ',slug';
		}
		$this->validate($this->request, $this->fields);
	}
	
	/**
	 * Cycle through the form fields and set them in the object.
	 */
	protected function setObjectData(){
		foreach($this->fields as $field => $rules){
			$this->object->$field = $this->request->$field;
		}
	}
	
	/**
	 * Save the object to the database
	 */
	protected function saveObject(){
		$this->object->save();
	}
	
	/**
	 * Delete the object from the database
	 */
	protected function deleteObject(){
		$this->object->delete();
	}

	/**
	 * Sync many-to-many relationships
	 */
	protected function handleIntermediaries($action){
		if(isset($this->intermediaries) && is_array($this->intermediaries)){
			foreach($this->intermediaries as $intermediary => $isAllowed){
				if($action == 'save' && $isAllowed['save']) {
					$this->syncIntermediary($intermediary);
				} elseif($action == 'destroy' && $isAllowed['destroy']){
					$this->detachIntermediary($intermediary);
				}
			}
		}
	}
	
	/**
	 * Sync intermediaries with the object
	 */
	protected function syncIntermediary($intermediary){
		if(
			is_array($this->request->$intermediary) &&
			count($this->request->$intermediary) > 0
		){
			$idList = $this->request->$intermediary;
		} else {
			$idList = [];
		}
		$this->object->$intermediary()->sync($idList);
	}
	
	/**
	 * Detach intermediaries from the object
	 */
	protected function detachIntermediary($intermediary){
		$this->object->$intermediary()->detach();
	}
	
	/**
	 * Set a success message in the session
	 */
	protected function setSuccessMessage($messageType){
		$messages = array(
			'save' => 'The ' . $this->objectLang . ' was successfully saved.',
			'destroy' => 'The ' . $this->objectLang . ' was successfully deleted.'
		);
		Session::flash('success', $messages[$messageType]);
	}
	
	/**
	 * Redirect to the next route
	 */
	protected function redirect($action){
		$route = $this->redirectAfter[$action];
		if(strpos($route, 'index') !== false){
			return redirect()->route($route);
		} else {
			return redirect()->route($route, $this->id);
		}
	}
}

Sample child controller

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Session;
use App\Product; // don't forget this!

class ProductController extends GenericResourceController
{
	protected $model = 'App\Product';
	protected $fields = [
		'product_name' => 'required|max:255',
		'description' => '',
		'slug' => 'required|alpha_dash|min:5|max:255',
		'price' => 'required|float',
		// etc...
	];
	protected $intermediaries = ['tags' => ['save' => true, 'destroy' => true]];
	protected $objectLang = 'product';
	protected $redirectAfter = [
		'store' => 'products.show',
		'update' => 'products.show',
		'destroy' => 'products.index',
	];
	
	public function index(){
	}
	public function show(){
	}
	public function create(){
	}
	public function edit(){
	}
}