I'm trying to store an image using Backpack's CRUD.
The Model's name is ProductModel, and in the SetupCreateOperation, I have:
CRUD::addField([
'name' => 'photo',
'label' => 'Foto',
'type' => 'image'
]);
When I try to upload an image, I get an error saying the following.
String data, right truncated: 1406 Data too long for column 'photo.'
Indeed the string being passed is almost 7000 characters long.
Model
class ProductModel extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
use HasFactory;
protected $guarded = [];
public function products()
{
return $this->hasMany('App\Models\SoldProduct',
'product_model_id', 'id');
}
}
Migration
Schema::create('product_models', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('photo'); //path
$table->integer('stock');
$table->integer('limited_edition_pieces');
$table->decimal('price', 8, 2);
$table->string('note')->nullable();
$table->timestamps();
});
What should I do?
In order to set Image you need to add Field like this
$this->crud->addField([
'name' => 'image',
'label' => 'Image',
'type' => 'upload',
'upload' => true
]);
use 'disk' => 'uploads' if you want to upload to s3(amazon), otherwise don't add it if you want to keep images in public folder.
Also keep in mind that your image attribute needs to be set in your model.
Like this,
public function setImageAttribute($value)
{
$attribute_name = "image";
// you can check here if file is recieved or not using hasFile()
$disk = "public";
$destination_path = "/uploads";
$this->uploadFileToDisk($value, $attribute_name, $disk, $destination_path);
}
The uploadFileToDisk() lies in your Backpack\CRUD\app\Models\Traits\CrudTrait which you have already added
Related
Can be possible to store a file uploaded to a related table?
Scenario: I have a usres table in database and another one pictures. Users Model have the following function
public function picture()
{
return $this->hasOne(Picture::class);
}
And the Picture Model have the following function.
public function user_picture()
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
Is possible to store the picture in pictures database table (id, user_id, img_path) from the UserCrudController store() function?
try something like this
public function store(Request $request)
{
Picture::create([
'user_id' => // get the user id from $request or auth()->user(),
'img_path' => $request->file('image')->store('images', 'public'),
]);
return // your view or something else
}
Let's say it is a registration form that need to insert an image. Instead of using the Picture model directly you can just do this :
public function store(Request $request)
{
$request->validate(...);
$user = User::create(...);
//It will ensure that the image belongs to the user.
$user->picture()->create([
'image_path' => $request->file('image')->store('images');
])
}
I resolved the issue with the following steps.
As per Laravel Backpack I added the input field in the Blade:
#include('crud::fields.upload', ['crud' => $crud, 'field' => ['name' => 'img1', 'label' => 'Image 1', 'type' => 'upload', 'upload'=> true, 'disk'=>'uploads', 'attributes' => ['id' => 'img1', 'capture' => 'user']]])
After this I added the function in the User Controller as follow:
$request->validate(['img1' => 'mimes:jpg,png,jpeg|max:5120']);
$fileModel = new Picture;
if($request->file()) {
$fileName1 = time().'_'.$request->img1->getClientOriginalName();
$filePath1 = $request->file('img1')->storeAs('uploads', $fileName1, 'public');
$fileModel->name = time().'_'.$request->img1->getClientOriginalName();
$fileModel->img1 = '/storage/' . $filePath1;
$fileModel->save();
}
With these lines of code I was able to store the related Picture with the User.
Thank you all for the guidelines.
I have a model File where save files of my app, it like:
class File{
public const IMAGE_TYPE = 'image';
public const AUDIO_TYPE = 'audio';
public const VIDEO_TYPE = 'video';
public const APPLICATION_TYPE = 'application';
protected $fillable = ['path', 'type', 'description', 'order', 'filable_type', 'filable_id'];
}
Suppose I have an Post model, it like:
class Post{
public function videos(){
return $this->morphMany(File::class, 'filable')
->where('type', File::VIDEO_TYPE);
}
public function images(){
return $this->morphMany(File::class, 'filable')
->where('type', File::IMAGE_TYPE);
}
}
When I get data of above relationships it's okay
But when I create a new file of post it is repetitive and easily make mistakes
$post->images()->create([
'path' => 'my-image.jpg',
'type' => File::IMAGE_TYPE,
]);
$post->videos()->create([
'path' => 'my-image.mp3',
'type' => File::VIDEO_TYPE,
]);
I want it look like:
$post->images()->create([
'path' => 'my-image.jpg',
]);
$post->videos()->create([
'path' => 'my-image.mp3',
]);
I don't need declare type per creating videos or images of a post.
How I can accomplish this!
Modal
// Change morphMany to hasMAny
public function videos()
{
return $this->hasMany(File::class, 'fileable')
->where('type', File::IMAGE_TYPE);
}
Controller
// You can do this
$vedioToCreate = $post->videos();
$vedioToCreate->path = 'my-image.mp3';
$vedioToCreate->save();
// Or you can do this
$post->videos()->create([
'path' => 'my-image.mp3',
]);
This if kind of a finicky problem, and the title may be a bit confusing, so I'll try and explain as best as I can.
Basically, I am creating a Recipe application, where users can create lists and add recipes to them.
I want the lists to only be able to have one instance of each recipe, so for example, if I add Recipe 10 to List number 1, I don't want the user to be able to add Recipe 10 any more to List 1.
Right now I've only used validation for checking if the meal_id (the id for the recipe) already exists in the recipes table, but with this I am only able to add each recipe to one list.
$this->validate($request, [
'name' => 'required',
'meal_id' => 'required|unique:recipes,meal_id',
'list_id' => 'required'
]);
My relations are simply that a Recipe hasOne RecipeList, and a RecipeList hasMany Recipes, but I'm fairly certain changing that up wont be the fix.
Recipe model
class Recipe extends Model
{
use HasFactory;
protected $fillable = [
'name',
'meal_id',
'list_id'
];
public function lists()
{
return $this->hasOne(RecipeList::class);
}
}
RecipeList model
class RecipeList extends Model
{
use HasFactory;
protected $fillable = [
'name',
'user_id'
];
public function user()
{
return $this->belongsTo(User::class);
}
public function recipes()
{
return $this->hasMany(Recipe::class);
}
}
Recipes table
public function up()
{
Schema::create('recipes', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('meal_id');
$table->unsignedBigInteger('list_id');
$table->timestamps();
});
}
However maybe I need to set up some form of pivot table?
Does anyone have any ideas on some smart way of achieving what I want through the validation, or any other ideas?
You can use Rule and create a custom validation to add your conditions.
Here I assume your condition is to add a unique restriction on both list_id and meal_id
$this->validate($request, [
'name' => 'required',
'list_id' => 'required',
'meal_id' => [
required,
unique:recipes,meal_id,
\Illuminate\Validation\Rule::unique('recipes')->where(function($query) use ($request){
$query->where('list_id', $request->list_id)
->where('meal_id', $request->meal_id);
}),
],
]);
Use firstOrCreate(). Read more here: https://laravel.com/docs/8.x/eloquent#retrieving-or-creating-models
Example:
$recipeData = $request->validate([
'name' => 'required',
'meal_id' => 'required',
'list_id' => 'required'
]);
$recipe = Recipe::firstOrCreate($recipeData);
This will use your 3 fields, name, meal_id & list_id and will either select one or create it if it doesn't exist already.
Alternatively you can do a query to check if it exists. If you want to return an error/message to the user explaining that the new one is a duplicate.
$existingRecipe = Recipe::where([
'meal_id' => $recipeData['meal_id'],
'list_id' => $recipeData['list_id'],
'name' => $recipeData['name']])
->first();
if(!is_null($existingRecipe)) {
//return exists
}
Of course, using rules will be more efficient, depends on what you want your UI to express.
See BABAK ASHRAFI's answer
I have two models :Product and category
which are linked by a one-to-many relationship. A category has several products. I would like to select specific columns from each model.
Here is the query I have, but I have all the columns with category_id, but I want the category name instead of id. How can I do that. Thank you in advance.
here is the method in controller
$products = Product::with('categories:id,name')->get();
if ($products) {
$response = ['api_status' => 1, 'api_message' => 'success', 'data' => $products];
return response()->json($response);
} else {
$response = ['api_status' => 0, 'api_message' => 'Error'];
return response()->json($response);
}
Here is category model
class Categorie extends Model
{
use HasFactory, SoftDeletes;
protected $fillable =['name','redirect'];
public function products()
{
return $this->hasMany(product::class);
}
}
and the product model is:
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'description',
'detail', 'img',
'categorie_id', 'onSale',
'costPrice', 'inStock', 'salePrice'
];
public function categories()
{
return $this->belongsTo(Categorie::class);
}
}
here is the response:
To modify the output of your model I'd suggest using an API resource. This will give you more granular control about how a resource is returned by the API. A resource is also the best point to modify certain values.
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'detail' => $this->detail,
'img' => $this->img,
'category_id' => $this->categorie->name,
'category_name' => $this->categorie->name,
'onSale' => $this->onSale,
'costPrice' => $this->costPrice,
'inStock' => $this->inStock,
'salePrice' => $this->salePrice,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'categories' => $this->categories ?? null,
];
}
}
This way you can manually specify which values your response should have.
In your controller you can include the populated array in your response by manually filling the toArray method with the current request object or just by using the resolve method which basically does the previous task for you:
$response = [
'api_status' => 1,
'api_message' => 'success',
'data' => ProductResource::collection($products)->resolve()
];
You can select particular fields from the relationship but you always need to select any keys involved in the relationship:
$products = Product::with('categories:id,name')->get();
Now each Product has its 'categories' loaded and those Category models only have the id and name fields.
Importantly:
The relationship categories is named incorrectly, it should be categorie in this case as the foreign key on Product is categorie_id and it is a singular relationship, it does not return multiple results.
Product::with('categorie:id,name')->get()
If you want to keep the name categories you would have to define the foreign key used when defining the belongsTorelationship, the second argument.
If you need to transform the structure of any of this that is a different thing and you will be walking into transformers or an API Resource.
Not sure how you want your data to look but this is the structure you will have by eager loading records, so if you need a different structure then what you get you will have to show an example.
I want to automatically create a slug and save it to the database based on the title entered into a form. Currently, this is how my controller is set up:
public function store(News $id) {
News::create($this->validateArticle());
return redirect('/news');
}
public function validateArticle() {
return request()->validate([
'title' => 'required',
'excerpt' => 'nullable',
'body' => 'nullable'
]);
}
How can I modify this code so that I automatically generate a slug based off of the title?
Thanks.
This is another option of how to do it. Or you could use Observer to observe the crating method like so news->slug= Str::slug($request->title);
public function store(Request $request)
{
$news= new News();
$news->title= $request->title;
$news->slug= Str::slug($request->title);
$news->excerpt= $request->excerpt;
$news->body= $request->body;
$news->save();
return redirect('/news');
}
Make use you import Str use Illuminate\Support\Str;