Many to Many Relationship Laravel 4 - php

I am working on a laravel-4 application. Currently It is coming together nicely and I've been getting my head around defining the relationships between the various table s of the database. However I've run into a problem that I'm having trouble solving.
In my db there is a resources table and tags table. There is a many to many relationship between them so I've also got a resource_tags table which has both tables id as the foreign keys.
Now, when I am creating a resource based on data provided by the user via a form I create the resource, check the type and decide on an action. Then I retrieve the tags of the resource and loop through them and create an entry into the Tags table.
My issue is placing information into the resource_tags table. Is there a method that can enable me to do this with relative ease?
This is my controller that is handling the form submission:
class SharedResourcesController extends BaseController {
//Add a shared Resource to the DB
//To do: Error checking and validation.
public function handleResource(){
//Create Object
$resource = new SharedResource;
$resource->title = Input::get('title'); //Title of resource
$resource->user_id = Input::get('user_id'); //User who uploads
$resource->book_id = Input::get('book_id'); //Book it is associated with
$resource->type_id = Input::get('type_id'); //Type of resource
//STORE LINKS
//if type is link... 1
if($resource->type_id == "1"){
$resource->web_link = Input::get('link');
}
//if type is video...2
if($resource->type_id == "2"){
$resource->vid_link = Input::get('link');
}
//UPLOADING
//If type is doc...3
if($resource->type_id == "3"){
if(Input::hasFile('file')){
$destinationPath = '';
$filename = '';
$file = Input::file('file');
$basename = Str::random(12);
$extension = $file->getClientOriginalExtension();
$destinationPath = public_path().'/file/';
$filename = Str::slug($basename, '_').".".$extension;//Create the filename
$file->move($destinationPath, $filename);
$resource->doc_link = $filename;
}
}
//if type is img...4
if($resource->type_id == "4"){
if(Input::hasFile('file')){
$destinationPath = '';
$filename = '';
$file = Input::file('file');
$basename = Str::random(12);
$extension = $file->getClientOriginalExtension();
$destinationPath = public_path().'/img/uploads/';
$filename = Str::slug($basename, '_').".".$extension;//Create the filename
$file->move($destinationPath, $filename);
$resource->img_link = $filename;
}
}
//TAGS
//Get the tags
$tags = Array();
$tags = explode(',', Input::get('tags'));
foreach($tags as $tag){
//Create a new Tag in DB - TO DO: Only Unique TAGS
$newTag = new Tag;
$newTag->name = $tag;
$newTag->save();
//Enter to resource tags
}
//Entry to resouce_tags
//Save Object
$resource->save();
return Redirect::action('User_BaseController#getSharedResources')->with('success', 'Resouce Created!');
//Any errors return to Form...
}
}
MODELS
class SharedResource extends Eloquent{
//set up many to many
public function tags(){
return $this->belongsToMany('Tag');
}
and
class Tag extends Eloquent{
//set up many to many
public function sharedResources(){
return $this->belongsToMany('SharedResource');
}
I know that there is lots missing in terms of validation and error handling, but I'm just trying to get the flow working and I can modify it at a later date. I'd appreciate any help.

All you have to do is build or grab the Resource and build or grab the Tags then call saveMany on the resource's tags relationship and pass an array of tag items into it, like this (pseudo-codey example):
$resource = Resource::create(['name' => 'Resource 1']);
$tag = [];
for ($i = 5; $i > 0; $i--) {
$tag = Tag::create(['name' => 'Tag '.$i]);
array_push($tags, $tag);
}
$resource->tags()->saveMany($tags);
The $tags have to be an array of Tag objects, and the saveMany called on the relationship will take care of the pivot table insertions for you. You should end up with a Resource 1 resource in the resources table, five Tags in the tag table, and 5 records in the resource_tag table with the relationships saved.

Can you add the code for both of your models as well? Do you have the relationship defined in them?
For example:
class Resource extends Eloquent {
public function tags()
{
return $this->belongsToMany('tag');
}
}
and
class Tag extends Eloquent {
public function resources()
{
return $this->belongsToMany('resource');
}
}

Related

Laravel foreign key with img path

I basically have this:
foreach ($request->input('images', []) as $imagesData) {
$images = new ScenesImages($imagesData);
$images->product()->associate($product);
$images->save();
}
This saves correctly in my foreign key the name of the image/s but how do I safe the image path dynamicly here.
If I dd($product);
I get this:
> #attributes: array:2 [▼
> "name" => "test"
> "product_id" => 7 ]
but no img(path)
Javascript which creates that html in a loop:
for(var i = 1; i<slider.value; i++) {
$('#sliderAppendSz').append(
'<div class=\"form-group\">'
+'<div class=\"fileinput fileinput-new\" data-provides=\"fileinput\">'
+'<div class=\"input-group\">'
+'<input name=\"images['+i+'][name]\" type=\"text\">'
+'<span class=\"input-group-btn\">'
+'<span class=\"btn btn-primary btn-file\">'
+'<input name=\"images['+i+'][scenes_images]\" type=\"file\" multiple class=\"ImageInput\" accept=\"file_extension/*\">'
+'</span>'
+'</span>'
+'</div>'
+'</div>');
}
And my ScenesImages Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ScenesImages extends Model
{
protected $table = 'scenes_images';
protected $fillable = ['product_id', 'name', 'scenes_images'];
public function product()
{
return $this->belongsTo('App\Product', 'product_id');
}
}
And here my Product Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
public static $projects = 'C:\\xampp\\htdocs\\MyProject\\Web';
protected $table = 'products';
protected $fillable = [
'scenes_images'
];
public static $rules = [
'scenes_images' => 'max:500',
];
public function scenesImages()
{
return $this->hasMany('App\ScenesImages');
}
}
Edit:
OK I think I got it I had to add this as since I only got input before I just added a foreach with a file, my question now is if it is somehow possible to sum the two foreach loops into one like $requesting->file and input.
foreach ($request->file('images', []) as $imagesData) {
$images = new ScenesImages($imagesData);
$images->product()->associate($product);
$images->save();
}
Could be better if you show us more information about the model ScenesImages, otherwise maybe you can have something like this.
foreach ($request->input('images', []) as $imagesData) {
$images = new ScenesImages($imagesData);
$images->product()->associate($product);
$images->path = $your_path
$images->save();
}
Of course you will need the variable $your_path with the path where is saved the images or if it's an url from Amazon S3, but as I said we need maybe more information about your Models structure.
Don't forget upload the image, save it to the server
foreach ($request->input('images', []) as $imagesData)
{
$images = new ScenesImages($imagesData);
$images->product()->associate($product);
$destinationPath = 'uploads'; // upload path
$extension = $imagesData->getClientOriginalExtension(); // getting image extension
$fileName = $imagesData->getClientOriginalName()'.'.$extension; // renameing image
$imagesData->move($destinationPath, $fileName); // uploading file to given path
$images->path = $destinationPath.'/'.$fileName;
$images->save();
}
Couple of issues I'm having with your code:
I'm a bit confused on what $request->file('images') returns. If it's an array of the UploadedFile object then I'm pretty sure you're doing it wrong. You're not even uploading the images to your server, once uploaded that's the file path you want to use when saving your ScenesImages model.
Your code here:
$images = new ScenesImages($imagesData);
is newing up a ScenesImages object and passing it a variable which would need to satisfy the model's required attributes to be created properly, its attributes being: name, scenes_images according to your question code. If you don't it's not going to work properly. You should be passing it an associative array with the keys matching the attributes the model has.
You should usually make your tables plural: scene_images and then your model singular: SceneImage
Consider doing your code like this:
$product = Product::first(); // assume you've defined this already
$uploadedImages = $request->file('images'); // get the images
$destinationPath = storage_path . '/uploads'; // somewhere to put your images
foreach ($uploadedImages as $uploadedImage) {
// upload the image first!
if ($uploadedImage->isValid()) {
$extension = $uploadedImage->getClientOriginalExtension(); // file extension
$uploadedImageName = uniqid(). '.' .$extension; // unique file name with extension
$uploadedImage->move($destinationPath, $uploadedImageName); // move file to our uploads path
// create ScenesImages record in DB
$image = ScenesImages::create([
'name' => $uploadedImageName, // assuming this is your file path?
'scenes_images' => '' // assign something here, not sure what it's supposed to be?
]);
// associate product
$image->product()->associate($product);
} else {
// handle error here
}
}

Laravel 5 - Clean code, where to keep business logic (controller example)

Below example of 'store' method of my controller Admin/MoviesController. It already seems quite big, and 'update' method will be even bigger.
The algoritm is:
Validate request data in CreateMovieRequest and create new movie
with all fillable fields.
Upload poster
Fill and save all important, but not required fields (Meta title, Meta Description..)
Then 4 blocks of code with parsing and attaching to movie of Genres, Actors, Directors, Countries.
Request of IMDB's rating using third-party API
My questions:
Should I just move all this code to Model and divide it into smaller methods like: removeGenres($id), addGenres(Request $request), ...
Are there some best practices? I'm talking not about MVC, but Laravel's features. At the moment to keep some logic behind the scene I'm using only Request for validation.
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$poster = \Image::make($request->file('poster'));
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = storage_path() . '/images/movies/'.$movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $request->file('poster')->getClientOriginalExtension();
$poster->save($path . $filename);
$movie->poster = $filename;
}
/* If 'Meta Title' is empty, then fill it with the name of the movie */
if ( empty($movie->seo_title) ) {
$movie->seo_title = $movie->title;
}
/* If 'Meta Description' is empty, then fill it with the description of the movie */
if ( empty($movie->seo_description) ) {
$movie->seo_description = $movie->description;
}
// Apply all changes
$movie->save();
/* Parsing comma separated string of genres
* and attaching them to movie */
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
foreach($genres as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$genre = Genre::where('name', $name)->first();
/* If such genre doesn't exists in 'genres' table
* then we create a new one */
if ( empty($genre) ) {
$genre = new Genre();
$genre->fill(['name' => $name])->save();
}
$movie->genres()->attach($genre->id);
}
}
/* Parsing comma separated string of countries
* and attaching them to movie */
if (!empty($request->input('countries'))) {
$countries = explode(',', $request->input('countries'));
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/* Parsing comma separated string of directors
* and attaching them to movie */
if (!empty($request->input('directors'))) {
$directors = explode(',', $request->input('directors'));
foreach($directors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
// Actors and Directors stored in the same table 'actors'
$director = Actor::where('fullname', trim($name))->first();
if ( empty($director) ) {
$director = new Actor();
$director->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_director' table
$movie->directors()->attach($director->id);
}
}
/* Parsing comma separated string of actors
* and attaching them to movie */
if (!empty($request->input('actors'))) {
$actors = explode(',', $request->input('actors'));
foreach($actors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$actor = Actor::where('fullname', $name)->first();
if ( empty($actor) ) {
$actor = new Actor();
$actor->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_actor' table
$movie->actors()->attach($actor->id);
}
}
// Updating IMDB and Kinopoisk ratings
if (!empty($movie->kinopoisk_id)) {
$content = Curl::get('http://rating.kinopoisk.ru/'.$movie->kinopoisk_id.'.xml');
$xml = new \SimpleXMLElement($content[0]->getContent());
$movie->rating_kinopoisk = (double) $xml->kp_rating;
$movie->rating_imdb = (double) $xml->imdb_rating;
$movie->num_votes_kinopoisk = (int) $xml->kp_rating['num_vote'];
$movie->num_votes_imdb = (int) $xml->imdb_rating['num_vote'];
$movie->save();
}
return redirect('/admin/movies');
}
You need to think on how you could re-utilize the code if you need to use it in another classes or project modules. For starting, you could do something like this:
Movie model, can improved in order to:
Manage the way on how the attributes are setted
Create nice functions in functions include/manage the data of relationships
Take a look how the Movie implements the functions:
class Movie{
public function __construct(){
//If 'Meta Title' is empty, then fill it with the name of the movie
$this->seo_title = empty($movie->seo_title)
? $movie->title
: $otherValue;
//If 'Meta Description' is empty,
//then fill it with the description of the movie
$movie->seo_description = empty($movie->seo_description)
? $movie->description
: $anotherValue;
$this->updateKinopoisk();
}
/*
* Parsing comma separated string of countries and attaching them to movie
*/
public function attachCountries($countries){
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/*
* Update Kinopoisk information
*/
public function updateKinopoisk(){}
/*
* Directors
*/
public function attachDirectors($directors){ ... }
/*
* Actores
*/
public function attachActors($actors){ ... }
/*
* Genders
*/
public function attachActors($actors){ ... }
}
Poster, you may considere using a service provider (I will show this example because I do not know your Poster model
looks like):
public class PosterManager{
public static function upload($file, $movie){
$poster = \Image::make($file);
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = config('app.images') . $movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $file->getClientOriginalExtension();
$poster->save($path . $filename);
return $poster;
}
}
Config file
Try using config files to store relevant application constanst/data, for example, to store movie images path:
'images' => storage_path() . '/images/movies/';
Now, you are able to call $path = config('app.images'); globally. If you need to change the path only setting the config file is necessary.
Controllers as injected class.
Finally, the controller is used as a class where you only need to inject code:
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$file = $request->file('poster');
$poster = \PosterManager::upload($file, $movie);
$movie->poster = $poster->filename;
}
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
$movie->attachGenders($genders);
}
// movie->attachDirectors();
// movie->attachCountries();
// Apply all changes
$movie->save();
return redirect('/admin/movies');
}

How to keep tracks of user(s) download(s) in Laravel 4?

I want to keep track of users download on my web application so I decide to create a tables called downloads. I already assign the relation in my model.
Download.php
<?php
use Illuminate\Auth\UserTrait;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableTrait;
use Illuminate\Auth\Reminders\RemindableInterface;
class Download extends Eloquent {
protected $table = 'downloads';
// Relations
public function user(){return $this->belongsTo('User','user_id');}
public function catalog_downloads(){return $this->hasMany('CatalogDownload'); }
public function marketing_materials(){return $this->hasMany('Download'); }
}
Here is my download function in one of my controller
public function file_download($id)
{
$catalog_download = CatalogDownload::findOrFail($id);
$distributor = Auth::user()->distributor()->first();
$export_type = $distributor->export_type()->first();
$product_export = $catalog_download->product_exports()->first();
$destinationPath = base_path().'/app/files/product_export/'. $catalog_download->id.'/'. $export_type->id.'/';
$file_name = $product_export->file_path;
$pathToFile = $destinationPath .$file_name;
if(Response::download()){
$download = new Download;
$download->title = $catalog_download->title;
$download->count = $download->count + 1;
$download->user_id = Auth::user()->id ;
$download->save();
}
return Response::download($pathToFile);
}
I have downloads table already.
For some reasons, no data has been save to the database. :(
Can someone help me take a look into this ?
I just added this block of code.
if(Response::download()){
$download = new Download;
$download->title = $catalog_download->title;
$download->count = $download->count + 1;
$download->user_id = Auth::user()->id ;
$download->save();
}
The rest is all correct.
you have a return statement before your code, it is unreachable, you cannot do anything after a return...

How to submit data to multiple tables in the same form

I have 2 tables: listings and listings_specifications
Listings table
id
type
status
location
specifications_id
Listings_specifications table
id
listing_id
swimming_pool
water_well
I need to select the specifications (checkboxes) on the same form with which I add a listing. I have created all the forms, views, models, controllers but I think I got some logic wrong.
Listing.php model
protected $table = 'listings';
public function contact()
{
return $this->BelongsTo('contacts');
}
public function specifications()
{
return $this->BelongsTo('listings_specifications');
}
Specification.php model
protected $table = 'listings_specifications';
public function listings()
{
return $this->BelongsTo('listings');
}
ListingsController.php (where the data gets saved in the database)
$listing = new Listing;
$contact = new Contact;
$listing->status = Input::get('status');
$listing->listingfor = Input::get('listingfor');
$listing->propertystatus = Input::get('propertystatus');
$listing->propertytype = Input::get('propertytype');
$listing->userid = Auth::user()->id;
$listing->location = Input::get('location');
$listing->contact_id = $contact_id;
$listing->save();
$specifications = Specification::find($id);
if( $listings->save() ) {
$specifications = new Specification;
$specifications->listing_id = $id;
$specifications->swimming_pool = Input::get('swimming_pool');
$specifications->water_front = Input::get('water_front');
$specifications->save();
}
I'm getting this error: Undefined variable: id
Where did I go wrong?
Thank you
It looks like you have some logic errors.
First of all, you are never setting $id anywhere, but that's okay because you really don't need it.
Remove the $specifications = Specification::find($id); line because that's not doing anything.
Then change your last section to something like this...
if( $listings->save() ) {
$specifications = new Specification;
$specifications->swimming_pool = Input::get('swimming_pool');
$specifications->water_front = Input::get('water_front');
$listing->specifications()->save($specifications);
}
$listing->specifications()->save($specifications); will automatically save the new specification with the correct listing_id for you.
Modify your Listing model's specifications relationship to...
public function specifications()
{
return $this->hasMany('Specification');
}
I'm assuming here one listing can have many specifications. If not, you can easily just change that to a hasOne.
You use $id in the line $specifications = Specification::find($id); but you don't define it before.

Sending Data to 2 MySQL tables - FuelPHP / PHP

I am adapting StationWagon (FuelPHP app) and so far it's working really well.
I have adapted it (with some help) to allow multiple images to be uploaded to the server. This is also working great.
However, I am thinking it would make more sense if I had 2 Tables: 1) Articles and 2) ArticleImages. I would use a Foreign Key to associate the Images with the Article. So when publishing an article it would add the article data to 'Articles' table and move each image to a new row in 'ArticleImages'.
So ultimately my 'ArticleImages' table could be:
ID | ImageURL | ArticleID
Portion of my 'articles.php' controller:
<?php
public function action_add()
{
$val = Model_Article::validate('add_article'); //<-- maybe its just me but i never saw any similar to this in fuelphp sorry about this if im wrong
// if your form validation is okay than continue with everyhing else
if ($val->run())
{
$article = Model_Article::forge();
// Custom configuration for this upload
$config = array(
'path' => DOCROOT.DS.'images',
'randomize' => true,
'ext_whitelist' => array('img', 'jpg', 'jpeg', 'gif', 'png'),
);
Upload::process($config);
// if a valid file is passed than the function will save, or if its not empty
if (Upload::is_valid())
{
// save them according to the config
Upload::save();
//if you want to save to tha database lets grab the file name
$value = Upload::get_files();
foreach($value as $files) {
print_r($files);
}
$article->filename = $value[0]['saved_as'];
}
$status = (Input::post('save_draft') ? 0 : 1);
if ( ! $val->input('category_id'))
{
$category_id = null;
}
else
{
$category_id = $val->validated('category_id');
}
$article->user_id = $this->user_id;
$article->category_id = $category_id;
$article->title = $val->validated('title');
$article->body = $val->validated('body');
$article->published = $status;
if ($article->save())
{
Session::set_flash('success', 'Article successfully added.');
}
else
{
Session::set_flash('error', 'Something went wrong, '.
'please try again!');
}
Response::redirect('articles/add');
}
$this->template->title = 'Add Article';
$this->template->content = View::forge('articles/add')
->set('categories', Model_Category::find('all'), false)
->set('val', Validation::instance('add_article'), false);
}
/* End of file articles.php */
You are trying to make a relation between Articles and ArticleImages. An ArticleImage belongs to an Article, while an Article has many ArticleImages. FuelPHP has functionality built in for what you are trying to achieve. Have a look at the FuelPHP docs on its Object Relational Mapper, especially the Belongs To and Has Many relations.
So when i made the code for you back a few days a go you only requested, one file input.
And no offense but you are doing it all wrong...
foreach($value as $files) {
print_r($files);
}
$article->filename = $value[0]['saved_as'];
should be
foreach($value as $files) {
$articleimg = Model_Articleimages::forge();
$articleimg->image_row_name = $files['saved_as']
}
To get you to understand
what you did here, $value = Upload::get_files(); yes this gets all the elements but since you need to loop trouh the elements you dont need it
Second
this $value[0]['saved_as'] only grabs the first image name, just the first one, and since you are in a loop now you need to refer to the $files variable as i shown you in the above example, just an example

Categories