I am very new to the Laravel, so I have a project for making a quiz. Currently I achieved to store my questions with radio-answers in the database, but I do not know how to:
1) Display all the questions with answers on web page.
2) Store the points for the user on each correct answer.
UPDATE: Thanks to ettdro I have solved my 1st problem. Only my 2nd left.
I would appreciate any help.
My Answer.php Model is empty for now. My Question.php Model:
class Question extends Model
{
// connect the models by adding a relationship to the Question model
public function answers()
{
return $this->hasMany(Answer::class);
}
}
My up function in Migration for Questions is:
public function up()
{
Schema::create('questions', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->integer('points')->unsigned();
$table->timestamps();
});
}
My up function in Migration for Answers is:
public function up()
{
Schema::create('answers', function (Blueprint $table) {
$table->id();
// since answer is connected to the question
$table->integer('question_id');
$table->string('text');
$table->boolean('correct_one');
$table->timestamps();
});
}
My QuestionAnswerSeeder.php is:
// for filling the tables
class QuestionAnswerSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
// truncating the tables and then store each question and its answers.
public function run()
{
Question::truncate();
Answer::truncate();
$questionAndAnswers = $this->getData();
$questionAndAnswers->each(function ($question) {
$createdQuestion = Question::create([
'text' => $question['question'],
'points' => $question['points'],
]);
collect($question['answers'])->each(function ($answer) use ($createdQuestion) {
Answer::create([
'question_id' => $createdQuestion->id,
'text' => $answer['text'],
'correct_one' => $answer['correct_one'],
]);
});
});
}
// for the actual data, I use a separate getData method to keep it cleaner
// in this method, I return a big collection with all the questions and answers
private function getData()
{
return collect([
[
'question' => 'When did the World War 2 end?',
'points' => '1',
'answers' => [
['text' => '1939', 'correct_one' => false],
['text' => '1941', 'correct_one' => false],
['text' => '1945', 'correct_one' => true],
],
],
[
'question' => 'Who discovered America?',
'points' => '1',
'answers' => [
['text' => 'Adolf Hitler', 'correct_one' => false],
['text' => 'Napoleon Bonaparte', 'correct_one' => false],
['text' => 'Christopher Columbus', 'correct_one' => true],
],
],
]);
}
}
You should have a QuestionController.php that has this content:
/**
* In this function, you need to get all the data you want to pass to your view
* and send it to the compact function in the return statement.
*/
public function index() {
// This will return a collection of questions including their answers.
$questionsCollection = Question::all();
return view('myquestionsview', compact('questionsCollection'));
}
Next, because you returned myquestionsview in the index function, you will need a file named: myquestionsview.blade.php in the views folder.
To display the informations of your questions, in your myquestionsview.blade.php you should have something like
#foreach ($questions as $question)
{{ $question->text }}
// Now, we want to display each answers of the question.
#foreach ($question->answers as $answer)
{{ $answer->text }}
#endforeach
#endforeach
This is basically what you want to do for your first question.
Related
This is a continuation of my last question.
I like to create a relationship between a user (with an account type that’s equal to a “profile”) and my job posts. What I did was create a relationship like this in my models (not sure if correct tho)
User.php
public function jobposts()
{
$this->hasMany(JobPost::class)->where('account_type', 'profile');
}
JobPost.php
public function userprofile()
{
$this->belongsTo(User::class)->where('account_type', 'profile');
}
JobPostController.php
public function store(Request $request)
{
$this->validate($request, [
'job_name' => 'required|max:100',
'describe_work' => 'required|max:800',
'job_category' => 'required|not_in:0',
'city' => 'required|not_in:0',
'state' => 'required|not_in:0',
'zip' => 'required|regex:/\b\d{5}\b/',
]);
dd(auth()->user()->jobpost()->job_name);
}
2021_11_20_211922_create_job_posts_table.php
Schema::create('job_posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->contrained()->onDelete('cascade');
$table->string('job_name');
$table->text('describe_work');
$table->string('job_category');
$table->timestamps();
});
Got 2 questions about what I can do in the JobPostController.php.
How do I dd() to test the output?
This seems wrong
dd(auth()->user()->jobpost()->job_name);
How do I add it correctly into the DB like this?
public function store(Request $request)
{
$request->user()
->jobpost()
->create([
'job_name' => $request->job_name
]);
}
In Laravel PHP Framework, when you have, let's say, a relationship between two tables e.g. one post can have one or multiple comments, you can create the comments of the post these ways:
// Option 1
$post->comments()->create(['text' => 'Greate article...']);
or
// Option 2
Comment::create([
'post_id' => 1,
'text' => 'Greate article...',
]);
Of course, it depends on the cases. Below are my cases.
For both options, the Post ID 1 has already been validated in the form request whether the post with the ID 1 does exist or not.
For some reasons, I already need to retrieve the post from the database first, thus I already have the post model.
In these above cases, is the Option 1 more expensive than the Option 2?
You can test the queries your application makes by listening to the queries using DB::listen().
I have setup the following as a test:
Migrations:
Schema::create('posts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->string('content');
$table->timestamps();
});
Schema::create('comments', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('post_id');
$table->string('text');
$table->timestamps();
});
Models:
class Post extends Model
{
protected $guarded = [];
public function comments()
{
return $this->hasMany(Comment::class);
}
}
class Comment extends Model
{
protected $guarded = [];
public function post()
{
return $this->belongsTo(Post::class);
}
}
Test:
$post = Post::create([
'title' => 'Hello World',
'content' => 'Here I am!',
]);
$queries = collect();
DB::listen(function ($query) use ($queries) {
$queries->push($query->time);
});
for ($i = 1; $i <= 3000; $i += 1) {
Comment::create([
'post_id' => $post->id,
'text' => 'Facade '.$i,
]);
$post->comments()->create([
'text' => 'Relation '.$i,
]);
}
$totalTime = [0, 0];
foreach ($queries as $idx => $time) {
$totalTime[$idx%2] += $time;
}
return [
'facade' => $totalTime[0],
'relation' => $totalTime[1],
];
This outputs:
array:2 [▼
"facade" => 1861.3
"relation" => 1919.9
]
So you can see the relation way of create is actually approximately 3% slower in my test scenario.
I have prepared this implode if you want to experiment further.
If it is "cheaper", it won't be by a noticeable amount. You're creating an instance of a Query builder, but you're not actually querying anything. So in this case it's only adding the post_id on the new model you're creating.
I don't think it's something you worry about too much.
I have a talent model which can have many educations which I want to populate using data factories. But populating using artisan tinker the education data cause "Array to string conversion". From what I see, I am not giving an array to be converted into a string. Below are the Education's model, migration & factory
Error Message
PHP Notice: Array to string conversion in C:/Core/.../vendor/laravel/framework/src/Illuminate/Support/Str.php on line 360
Received On Running This Statement
$talents->each( function($talent) { factory(App\Education::class)->create(['talent_id' => $talent->id]); })
class Education extends Model
{
protected $fillable = [
'talent_id',
'institution',
'education_level',
'other_education_level',
'qualification_field',
'start_date_month',
'start_date_year',
'end_date_month',
'end_date_year',
];
public function talent() {
return $this->belongsTo(Talent::class);
}
}
Schema::create('educations', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('talent_id')->index();
$table->string('institution');
$table->string('education_level');
$table->string('other_education_level')->nullable();
$table->string('qualification_field');
$table->unsignedTinyInteger('start_date_month');
$table->unsignedSmallInteger('start_date_year');
$table->unsignedTinyInteger('end_date_month')->nullable();
$table->unsignedSmallInteger('end_date_year')->nullable();
$table->timestamps();
});
$factory->define(Education::class, function (Faker $faker) {
return [
'talent_id' => factory(\App\Talent::class),
'institution' => $faker->word,
'education_level' => $faker->word,
'other_education_level' => $faker->word,
'qualification_field' => $faker->words,
'start_date_month' => rand(1, 12),
'start_date_year' => rand(1970, 2000),
'end_date_month' => rand(1, 12),
'end_date_year' => rand(1970, 2000),
];
});
Below are the tinker commands I ran
$talents = App\Talent::all()
$talents->each( function($talent) { factory(App\Education::class)->create(['talent_id' => $talent->id]); })
The $talents->each( function($talent) { factory(App\Education::class)->create(['talent_id' => $talent->id]); }) is the cause, but I don't understand why
The same command with different class model works say for example
$talents->each( function($talent) { factory(App\WorkExperience::class)->create(['talent_id' => $talent->id]); })
class WorkExperience extends Model
{
protected $fillable = [
'talent_id',
'title',
'employment_type',
'company',
'start_date_month',
'start_date_year',
'end_date_month',
'end_date_year',
'description',
];
public function talent() {
return $this->belongsTo(Talent::class);
}
}
Schema::create('work_experiences', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('talent_id')->index();
$table->string('title');
$table->string('employment_type');
$table->string('company');
$table->unsignedTinyInteger('start_date_month');
$table->unsignedSmallInteger('start_date_year');
$table->unsignedTinyInteger('end_date_month')->nullable();
$table->unsignedSmallInteger('end_date_year')->nullable();
$table->text('description');
$table->timestamps();
});
$factory->define(WorkExperience::class, function (Faker $faker) {
return [
'talent_id' => factory(\App\Talent::class),
'title' => $faker->word,
'employment_type' => $faker->word(['Internship', 'Part Time', 'Full Time']),
'company' => $faker->word,
'start_date_month' => rand(1, 12),
'start_date_year' => rand(1970, 2000),
'end_date_month' => rand(1, 12),
'end_date_year' => rand(1970, 2000),
'description' => $faker->paragraph,
];
});
This question was initially posted from the Laracasts website forum and I've just received the solution.
The issue was in my EducationFactory:
'qualification_field' => $faker->words
$faker->words return an array of strings. The solution is to use $faker->sentence
'qualification_field' => $faker->sentence
Your issue probably lies in this piece of code:
'talent_id' => factory(\App\Talent::class),
The factory method will return an array of data for the Talent class, and will try to apply it to the talent_id , and fail.
To correct your problem, do:
'talent_id' => function () { return factory(\App\Talent::class)->create()->id; }
I am new to Laravel API, I want to return a book where recommended === 1
In my resources, I have this
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'about' => $this->about,
'content' => $this->content,
// 'image' => asset('/storage/'.$this->image),
'image' => $this->image_url,
// 'recommended' => $this->recommended,
'recommended' => $this->when($this->recommended === 1, $this->recommended),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'author' => $this->author,
];
I want to return books when recommended === 1
My table is Like this
public function up()
{
Schema::create('books', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->text('about');
$table->string('image');
$table->string('image_url');
$table->string('epub_url');
$table->integer('author_id');
$table->string('publisher');
$table->year('year');
$table->boolean('recommended')->default(0);
$table->timestamps();
});
I was able to achieve the same thing on web using this
public function index()
{
$data = array();
$data['recommends'] = Book::where('recommended', 1)->take(10)->get();
$data['latests'] = Book::orderBy('created_at', 'desc')->take(10)->get();
return view('welcome', compact("data"));
}
But I don't know how to replicate the same using Laravel API.
UPDATE
I was able to achieve the same thing on web using this
public function index()
{
$data = array();
$data['recommends'] = Book::where('recommended', 1)->take(10)->get();
$data['latests'] = Book::orderBy('created_at', 'desc')->take(10)->get();
return view('welcome', compact("data"));
}
But I don't know how to replicate the same using Laravel API.
Normally I will get all Books or Post like this using API Resource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'about' => $this->about,
'content' => $this->content,
'image' => $this->image_url,
'recommended' => $this->recommended,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'author' => $this->author,
];
and call it like this in my controller
public function indexapi()
{
return BookResource::collection(Book::with('author')->Paginate(16));
}
But there some cases recommended is == 1 and some recommended == 0, in this case, I want to return data only when recommended == 1
I know my question is quite confusing
Thanks.
Thanks.
If I get it right, you want to filter & get only the books with ( recommended attribute == 1 ). If thats the case you shouldn't do it in your Collection file. You should do this filtering process in your Controller before passing any data to Collection.
Here is some code example from one of my project.
In ProductController.php FILE
public function index()
{
return new ProductCollection( Product::where('recommended','1')->get() );
}
As you can see , I'm filtering the products to get only the recommended ones. Then I'm sending this filtered data to the ProductCollection. This way The collection will only return the data I want.
In ProductCollection.php FILE
public function toArray($request)
{
return [
'data' => $this->collection->map( function($data) {
return [
'id' => (integer) $data->id,
'name' => $data->name,
'category_id' => $data->category_id,
'brand_id' => $data->brand_id,
'photos' => json_decode($data->photos),
'gtin' => $data->gtin
];
})
];
}
I don't have to make any changes in Collection. Because in this way , Collection should do the job for every data it gets.
I have a model Foo, which has many Bars:
class Foo extends Model
{
public function bars()
{
return $this->hasMany('App\Bar');
}
}
class Bar extends Model
{
public function foo()
{
return $this->belongsTo('App\Foo');
}
}
When saving a new Foo, the request payload comes with an array of Bar ids. I want to save these at the same time. This works:
public function store(StoreFoo $request)
{
$foo = Foo::create($request->validated());
foreach ($request->barIds as $barId) {
$foo->bars()->create(['bar_id' => $barId]);
}
}
My question is: is there a way to do this without a loop? I've tried sync and attach but these aren't applicable in this case.
The only way I can think of that you can achieve this without writing a loop yourself is by using the saveMany method on the HasMany relation. You can create instances of your Bar model and pass them all as an array to the saveMany method and that will save all of them and return an array of the created entities in response.
$foo->bars()->saveMany([new Bar(['id' => 1]), new Bar(['id' => 2])]);
That being said, Laravel uses a loop to save these models one by one under the hood so it doesn't really do much different to what you're doing now.
Similarly, there's also a createMany method that you can use in the same way as saveMany but instead of providing newly created models, you can provide arrays of attributes instead.
migration table sample
Schema::create('logs', function(Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id')->default(0)->index();
$table->string('type', 10)->index(); // add, update, delete
$table->string('table', 50)->index();
$table->unsignedBigInteger('row');
$table->dateTime('created_at');
});
Schema::create('log_fields', function(Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('log_id')->index();
$table->string('field', 50)->index();
$table->longText('old');
$table->longText('new');
});
model Log.php file
class Log extends Model
{
const UPDATED_AT = null;
protected $fillable = [
'user_id',
'type',
'table',
'row'
];
public function logFields()
{
return $this->hasMany(LogField::class);
}
}
model LogField.php file
class LogField extends Model
{
public $timestamps = false;
protected $fillable = [
'field',
'old',
'new'
];
public function log()
{
return $this->belongsTo(Log::class);
}
}
boot function for another model for save change in database.
hook created, updating and deleting for answer your question
public static function boot()
{
parent::boot();
static::created(function($resorce) {
$_log = new Log;
$_log->create([
'user_id' => session('uid', 0),
'type' => 'add',
'table' => $resorce->getTable(),
'row' => $resorce->fresh()->toArray()['id']
]);
return true;
});
static::updating(function($resorce) {
$_log = new Log;
$log = $_log->create([
'user_id' => session('uid', 0),
'type' => 'update',
'table' => $resorce->getTable(),
'row' => $resorce->fresh()->toArray()['id']
]);
foreach($resorce->getDirty() as $field => $new) {
$log->logFields()->create([
'field' => $field,
'old' => $resorce->fresh()->toArray()[$field],
'new' => $new
]);
}
return true;
});
static::deleting(function($resorce) {
$_log = new Log;
$log = $_log->create([
'user_id' => session('uid', 0),
'type' => 'delete',
'table' => $resorce->getTable(),
'row' => $resorce->id,
]);
foreach($resorce->fresh()->toArray() as $field => $value) {
$log->logFields()->create([
'field' => $field,
'old' => '',
'new' => $value
]);
}
return true;
});
}
Hope I have helped you to understand this.