I am working on a blogging application in Laravel 8 .
The application supports themes. In a nutshell, theme support works like this:
In the views
directory, I have the directory containing the views of every theme.
In public\themes
I keep the assets (CSS, Javascript) of every theme.
screenshot of themes directory
In the SettingsController controller I have, among others, a variable named $theme_directory, which contains the name of the directory of the current theme. I use this variable in the theme view, like this, for instance:
<link href="{{ asset('themes/' . $theme_directory . '/css/clean-blog.min.css') }}" rel="stylesheet">
From the "Settings" section I can set the current theme.
I have added a "theme picker": a select-box containing all the available themes to pick from.
In the controller, I have a themes()
method that reads the names of the themes directories in the views and displays them:
namespace App\Http\Controllers\Dashboard;
use App\Http\Controllers\Controller;
use App\Models\Settings;
use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
class SettingsController extends Controller {
private $rules = [
'site_name' => 'required|string|max:190',
'tagline' => 'required|string|max:190',
'owner_name' => 'required|string|max:190',
'owner_email' => 'required|email|max:190',
'twitter' => 'required|string|max:190',
'facebook' => 'required|string|max:190',
'instagram' => 'required|string|max:190',
'theme_directory' => 'required',
];
private $messages = [
'site_name.required' => 'A site title is required',
'tagline.required' => 'A site tag line is required',
'owner_name.required' => 'Please provide a site owner/company name',
'owner_email.required' => 'A valid email is required',
'owner_email.email' => 'The email you provided is not valid',
'twitter.required' => 'Please provide a Twitter account',
'facebook.required' => 'Please provide a Facebook account',
'instagram.required' => 'Please provide an Instagram account',
'theme_directory.required' => 'Please pick a theme',
];
public function themes() {
$themes = [];
$themes_path = Config::get('view.paths')[0] . '\themes';
foreach(File::directories($themes_path) as $theme) {
$slug = array_reverse(explode('\\', $theme))[0];
$name = ucwords(str_replace('-', ' ', $slug));
$themes[] = (object)compact('slug', 'name');
}
return $themes;
}
public function index() {
$settings = Settings::first();
return view('dashboard/settings', [
'settings' => $settings,
'themes' => $this->themes()
]);
}
public function update(Request $request) {
$validator = Validator::make($request->all(), $this->rules, $this->messages);
if ($validator->fails()) {
return redirect()->back()->withErrors($validator->errors())->withInput();
} else {
$settings = Settings::first();
$settings->site_name = $request->get('site_name');
$settings->tagline = $request->get('tagline');
$settings->owner_name = $request->get('owner_name');
$settings->owner_email = $request->get('owner_email');
$settings->twitter = $request->get('twitter');
$settings->facebook = $request->get('facebook');
$settings->instagram = $request->get('instagram');
$settings->theme_directory = $request->get('theme_directory');
$settings->is_cookieconsent = $request->get('is_cookieconsent') == 'on' ? 1 : 0;
$settings->is_infinitescroll = $request->get('is_infinitescroll') == 'on' ? 1 : 0;
$settings->save();
return redirect()->route('dashboard.settings')->with('success', 'The settings were updated!');
}
}
}
In the view (form), I have:
<div class="row mb-2">
<label for="theme" class="col-md-12">{{ __('Theme directory') }}</label>
<div class="col-md-12 @error('theme_directory') has-error @enderror">
<select name="theme_directory" id="theme" class="form-control @error('theme_directory') is-invalid @enderror">
<option value="">Pick a theme</option>
@foreach($themes as $theme)
<option value="{{ $theme->slug }}" {{ $theme->slug == $settings->theme_directory ? 'selected' : '' }}>{{ $theme->name }}</option>
@endforeach
</select>
@error('theme_directory')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
Questions:
- Is there any redundancy in my code?
- Do you see any security issues?
1 Answer 1
Is there any redundancy in my code?
There is some redundancy between $rules
and $messages
This may not be an orthodox idea by many Laravel programmers but $rules
could be generated on the fly by iterating over the keys of $messages
- Something like:
$rules = array_reduce(array_keys($messages), function($carry, $messageKey) {
[$field] = explode('.', $messageKey);
if (str_contains($field, 'theme')) {
return $carry + [$field => 'required'];
}
$type = str_contains($field, 'email') ? 'email' : 'string';
return $carry + [$field => "required|$type|max:190"];
}, []);
The update
method repeatedly calls $request->get()
That method calls $request->get()
ten times. Many of those could be called in a loop - e.g. which iterates over the fields from $messages
.
As I explained in this answer a subclass of form request could be created to encapsulate all of the validation logic and simplify the setting of validated fields in the model.
If such a form request was made, then most all of those lines calling $request()->get()
could be replaced with a single call to $request->validated()
using the update()
method.
Do you see any security issues?
Can any user update update the settings? If not, then perhaps a role should be checked on the user.
Another benefit of form requests is that they can help with authorization - e.g. the authorize()
method can be defined and have it return false
for any user that should not be able to submit such a request.