I am not bitching or anything, but code examples would be much more efficient if you wanna get help from fellow Laracasts members
File upload/edit form.
I always having a trouble implementing a page where, for example user can create a new post. There is a post form and a file uploader to upload a thumbnail or etc. This part seems OK, I can check in validator whether file is set, if yes then try to upload it ant save its path to the database and everything is great. But problems start to arise when I need to add an Editing capability. For example user added a post and later I need to make an edit screen. I usually have a text field where I save a name of the current thumbnail and when user submits the form I check if file is set, if yes, then check if its name is the same as it was previous on the database, if no then remove old one and add new one. It makes a lot of mess and ugly code for me in this part, I wonder if there is a cleaner solution, that I could reuse over and over again. It would be nice if you could share your experience with such a use case and give an example how you implement this.
This is post-add.blade.php
@extends('backend.layout.master')
@section('content')
<div class="row">
<div class="col-lg-8">
<div class="form-group title-block">
<input type="text" class="form-control" placeholder="Title" name="title" value="{{ old('title') }}" required>
</div>
<textarea placeholder="Description" name="content" required>{{ old('content') }}</textarea>
</div>
<div class="col-lg-4">
<div class="ibox">
<div class="ibox-title">
<h5>Post Details</h5>
</div>
<div class="ibox-content">
<label class="m-t-xs">SEO Title</label>
<input type="text" placeholder="Seo Title" name="seo_title" class="form-control m-b" value="{{ old('seo_title') }}">
<label class="m-t-xs">SEO Description</label>
<textarea placeholder="Seo Description" name="seo_description" class="form-control">{{ old('seo_description') }}</textarea>
</div>
</div>
<div class="ibox">
<div class="ibox-title">
<h5>Featured Image</h5>
</div>
<div class="ibox-content">
<div style="width:100%;">
<div class="fileinput fileinput-new input-group m-b-none" data-provides="fileinput">
<div class="form-control" data-trigger="fileinput"><i class="fa fa-image m-r-xs"></i> <span class="fileinput-filename">No file selected</span></div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Select file</span><span class="fileinput-exists">Change</span><input type="hidden"><input type="file" name="featured_image" value=""></span>
<a href="#" class="input-group-addon btn btn-default fileinput-exists" data-dismiss="fileinput">Remove</a>
</div>
</div>
<button class="btn btn-primary" style="margin-top: 20px; margin-bottom: 20px;" name="publish">Add</button>
</div>
</div>
@if($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
</form>
This is post-edit.blade.php
@extends('backend.layout.master')
@section('content')
<div class="row">
<div class="col-lg-8">
<div class="form-group title-block">
<input type="text" class="form-control" placeholder="Title" name="title" value="{{ $post->title }}" required>
</div>
<textarea placeholder="Description" name="content" required>{{ $post->content }}</textarea>
</div>
<div class="col-lg-4">
<div class="ibox">
<div class="ibox-title">
<h5>Post Details</h5>
</div>
<div class="ibox-content">
<label class="m-t-xs">SEO Title</label>
<input type="text" placeholder="Seo Title" name="seo_title" class="form-control m-b" value="{{ $post->seo_title }}">
<label class="m-t-xs">SEO Description</label>
<textarea placeholder="Seo Description" name="seo_description" class="form-control">{{ $post->seo_description }}</textarea>
</div>
</div>
<div class="ibox">
<div class="ibox-title">
<h5>Featured Image</h5>
</div>
<div class="ibox-content">
<div style="width:100%;">
@if(!isset($post->featured_image))
<div class="fileinput fileinput-new input-group m-b-none" data-provides="fileinput">
<div class="form-control" data-trigger="fileinput"><i class="fa fa-image m-r-xs"></i> <span class="fileinput-filename">No file selected</span></div>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Select file</span><span class="fileinput-exists">Change</span><input type="hidden"><input type="file" name="featured_image" value=""></span>
<a href="#" class="input-group-addon btn btn-default fileinput-exists" data-dismiss="fileinput">Remove</a>
</div>
@else
<div class="fileinput input-group m-b-none fileinput-exists" data-provides="fileinput">
<div class="form-control" data-trigger="fileinput"><i class="fa fa-image m-r-xs"></i> <span class="fileinput-filename"><?=$post->featured_image;?></span></div>
<input type="hidden" name="featured_image_name" value="<?=$post->featured_image;?>"/>
<span class="input-group-addon btn btn-default btn-file"><span class="fileinput-new">Select file</span><span class="fileinput-exists">Change</span><input type="hidden"><input type="hidden" value="" name=""><input type="file" name="featured_image" value="<?=$post->featured_image;?>"></span>
<a href="#" class="remove-img input-group-addon btn btn-default fileinput-exists" data-dismiss="fileinput">Remove</a>
</div>
@endif
</div>
<button class="btn btn-primary" style="margin-top: 20px; margin-bottom: 20px;" name="publish">Update</button>
</div>
</div>
@if($errors->any())
<div class="alert alert-danger">
@foreach ($errors->all() as $error)
<div>{{ $error }}</div>
@endforeach
</div>
@endif
</div>
</div>
</div>
</form>
This is Post Service. It validates input, adds/updates and etc.
This is Post Service. It validates input, adds/updates and etc. (For some reason in a previous comment it didnt show up.)
namespace App\Model\Services;
use App\Model\Contracts\PostServiceInterface; use App\Model\Contracts\PostRepositoryInterface; use App\Model\Contracts\SlugRepositoryInterface; use Illuminate\Support\Facades\Validator; use Illuminate\Filesystem\Filesystem; use App\Model\Services\SlugHelper;
class PostService implements PostServiceInterface {
protected $postRepo;
protected $slugRepo;
private $validator;
private $data;
private $image;
private $slugHelper;
public function __construct(PostRepositoryInterface $postRepo, SlugRepositoryInterface $slugRepo)
{
$this->slugHelper = new SlugHelper();
$this->postRepo = $postRepo;
$this->slugRepo = $slugRepo;
}
public function validate($request, $edit = false) {
$this->data = [
'title' => $request->input('title'),
'content' => $request->input('content'),
'seo_title' => $request->input('seo_title'),
'seo_description' => $request->input('seo_description'),
'type' => 0,
'status' => 1
];
if($edit) {
$this->validator = Validator::make($request->all(), [
'title' => 'required',
'content' => 'required',
'featured_image_name' => 'required',
]);
}else{
$this->validator = Validator::make($request->all(), [
'title' => 'required',
'content' => 'required',
'featured_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
}
$this->image = $request->featured_image;
return !$this->validator->fails();
}
public function errors() {
return $this->validator != null ? $this->validator : null;
}
public function pagination($perPage) {
$posts = $this->postRepo->pagination($perPage);
foreach($posts as $post) {
$post->date_created = date('j M, Y', strtotime($post->date_created));
$post->description = strip_tags($post->content);
$post->description = strlen($post->description) > 140 ? mb_substr($post->description, 0, 140) . '...' : $post->description;
if(file_exists($_SERVER['DOCUMENT_ROOT'].'/images/posts/' . $post->id . '/' . $post->featured_image)) {
$post->featured_image = '/images/posts/' . $post->id . '/' . $post->featured_image;
}else{
$post->featured_image = 'http://via.placeholder.com/350x150?text=' . $post->title;
}
}
return $posts;
}
public function search($keyword) {
$posts = $this->postRepo->search($keyword);
$results = [];
foreach($posts as $post) {
$results[] = [
'label' => $post->title,
'value' => $post->title,
'href' => '/backend/posts/edit/' . $post->slug->slug
];
}
return $results;
}
public function store() {
$post = $this->postRepo->create($this->data);
$imageName = $this->image->getClientOriginalName();
$this->image->move(public_path('images/posts/' . $post->id), $imageName);
$data['featured_image'] = $imageName;
$this->postRepo->update($post->id, $data);
$slug_data = [
'type_id' => $post->id,
'type_type' => 'App\Model\Models\Post',
'slug' => $this->slugHelper->createSlug($this->data['title'])
];
$post = $this->slugRepo->create($slug_data);
return true;
}
public function get($slug) {
$postId = $this->slugRepo->getTypeId($slug, 'App\Model\Models\Post');
$post = $this->postRepo->get($postId->type_id);
$post->date_created = date('j M, Y', strtotime($post->date_created));
return $post;
}
public function getById($id) {
return $this->postRepo->get($id);
}
public function update($id) {
$post = $this->getById($id);
if ($post->title != $this->data['title']) {
$slug_data['slug'] = $this->slugHelper->createSlug($request->slug, $post->id);
$this->slugRepo->update($post->slug->id, $slug_data);
}
if($this->image != null) {
$imageName = $this->image->getClientOriginalName();
$this->image->move(public_path('images/posts/' . $post->id), $imageName);
$this->data['featured_image'] = $imageName;
}
$this->postRepo->update($post->id, $this->data);
return true;
}
public function delete($id) {
$post = $this->getById($id);
\File::deleteDirectory(public_path('images/posts/' . $id));
$this->slugRepo->delete_slug($post->id, 'App\Model\Models\Post');
$this->postRepo->delete($id);
}
}
First of all, do you know the route model binding? and what is the slug for when you update?
And what's the point of doing this if ($post->title != $this->data['title'])?
When you create a model you validate a picture with this 'featured_image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
but when editing you do 'featured_image_name' => 'required', ?
I'm sorry this is not my code so I need to know everything before giving you an advice
No, I never heard of route model binding. I update a slug on update because if user changes post title, it needs to update its slug based on what is a current title. if ($post->title != $this->data['title'] this line checks that (if title in user edit form isnt the same as it was in a database, then I update its slug).
On "add" method validation rules differs, because when you add post, you have to upload a file as a thumbnail. But when you edit, you don't need to do that, cause if you want to keep an existing thumbnail, that valiation rule used to show me errors, as I said before, I usually use a hidden text field on my edit forms, where I place existing featured image name. 'featured_image_name' => 'required'
this rule checks on edit form, if it is set. So what it basically does, if user edits a post but doesnt change a thumbnail validator still succeeds, otherwise I used to get a validation fail message, that featured image is required.
wow you overcomplicated your code. Would you paste the SlugRepo or I should say every repo?
SlugRepo
class SlugRepository extends Repository implements SlugRepositoryInterface {
function model()
{
return 'App\Model\Models\Slug';
}
public function getTypeId($slug, $type) {
$this->makeModel();
return $this->model->where('slug', '=', $slug)->where('type_type', '=', $type)->first();
}
public function delete_slug($id, $type) {
$this->makeModel();
$this->model->where('type_id', $id)->where('type_type', $type)->first()->delete();
}
}
PostRepo
class PostRepository extends Repository implements PostRepositoryInterface {
function model()
{
return 'App\Model\Models\Post';
}
public function pagination($itemsPerPage) {
$this->makeModel();
return $this->model->orderBy('date_created', 'desc')->paginate($itemsPerPage);
}
public function search($keyword) {
$this->makeModel();
$keyword = '%' . $keyword . '%';
return $this->model->where('title', 'like', $keyword)->get();
}
public function getBySlug($slug) {
$this->makeModel();
return $this->model->where('slug', '=', $slug)->first();
}
}
Please or to participate in this conversation.