_joinData not updating request correctly in Cakephp 3 - php

I am trying to create an associated table, where I've exam_id and question_id that are linked with belongsToMany relationship through an ExamsQuestions table that has belongsTo relationship on both of them.
I've created the (atleast some of the) relations correctly, as I can save the ids correctly, but I've in the associative table also fields "questionPoints" and "isActive" fields, but they're not updating.
I use .js file to send a run-time request.
But when I'm getting response from the controller function, the joindata is not set to the objects correctly.
in Database the rows are not updated at all, even though some of the ._joindata information is applied (ids). I think this is CakePHP's built-in association that does the joining, as 1 level of associations is applied by default.
In ExamsController I first send data to the view, in order to render the view correctly. After that I use the request data to patch entity and save the entity.
public function take($id)
{
if ($this->request->is('get'))
{
$exam = $this->Exams->get($id, [
'contain' => ['ExamTemplates' => ['Questions.Answers']]
]);
$users = $this->Exams->Users->find('list', ['limit' => 200]);
$examTemplates = $this->Exams->ExamTemplates->find('list', ['limit' => 200]);
$this->set(compact('exam', 'users', 'examTemplates'));
}
if ($this->request->is('post')) {
$exam = $this->Exams->get($id, ['contain' => ['Questions']]);
$this->autoRender = false;
$exam = $this->Exams->patchEntity($exam, $this->request->data);
if ($this->Exams->save($exam)) {
$response = [
'success' => true,
'message' => __("exam updated"),
'this' => $this->request->data,
];
$exam_id = $_POST['id'];
$this->Flash->success(__('The exam has been updated.'));
} else {
$response = [
'success' => false,
'error' => $exam->errors(),
'request' => $this->request->data,
'message' => __("Error creating template")
];
}
$this->response->body(json_encode($response));
}
}
Thanks.

Okay, I fixed the problem, but I don't know if the fix was done the most correct way.
Instead of updating the field in AJAX call, I changed the _joinData update to happen in the controller based on request data.
So I just declared a new _joinData as new array and gave it the fields that were not automatically added. One thing to note is that if I gave it any field manually that CakePHP does autocomplete, it would also not update.

Related

Read data from an API and populate the database without duplicate in laravel

Controller code to view the API records
public function index()
{
$client=new Client();
$response = $client->request('GET', 'https://api.publicapis.org/entries');
$apidata = json_decode($response->getBody()->getContents(),true);
return view('apidata')->with('apidata', $apidata ['entries']);
}
to store the records into db
public function store()
{
set_time_limit(240);
$client=new Client();
$response = $client->request('GET', 'https://api.publicapis.org/entries');
$apidata = json_decode($response->getBody()->getContents(),true);
foreach($apidata ['entries'] as $entries) {
$entries = Apidata::updateorCreate([
'API'=> $entries['API'],
'Description'=>$entries['Description'],
'Auth'=>$entries['Auth'],
'HTTPS'=>$entries['HTTPS'],
'Cors'=>$entries['Cors'],
'Link'=>$entries['Link'],
'Category'=>$entries['Category']
]);
}
return redirect()->back()->with('message', 'Data Saved Successfully!');
}
}
Get records from external API
Save all the records into database
The records must not be duplicated in case we send multiple API calls
also how do i save the records in batch to avoid maximum time out for large entries
In your case you were using updateOrCreate method. So you can set a primary key field to determine if the same record occurs again, it will automatically just update the same record with the primary column.
Let us assume - "Auth" is a primary column, then you can save the records with updateOrCreate method as follows,
$entries = Apidata::updateorCreate(
[
'API' => $entries['API'],
],
[
'API' => $entries['API'],
'Description' => $entries['Description'],
'Auth' => $entries['Auth'],
'HTTPS' => $entries['HTTPS'],
'Cors' => $entries['Cors'],
'Link' => $entries['Link'],
'Category' => $entries['Category']
]
);
In such way you can avoid duplicate entries in laravel 9

Laravel hasMany is Return No Results

In Laravel, I am creating a message thread feature. My schema looks like this:
MessageThreads Table
column
id
MessageThreadParticapants Table
column
thread_id
user_id
And I have the corresponding models of MessageThread and MessageThreadParticapant. In the MessageThread model, I have the following relation:
public function users() {
return $this->hasMany(MessageThreadParticapant::class, 'thread_id', 'id');
}
Here is where things get funny. If I do:
MessageThread->users
I get an empty result. But if I do:
MessageThreadParticapant::where('thread_id', $same_thread_id)->get()
I get the correct amount of results back. What am I doing wrong here?
UPDATE
One of the suggestions was "hasMany(Model, 'foreign_key', 'local_key')" to be incorrect. Some more context,its failing my unit tests. I'm testing up a test as such:
public function testUsers() {
$thread1 = MessageThread::factory()->create();
$thread2 = MessageThread::factory()->create();
$this->assertCount(0, $thread1->users);
$this->assertCount(0, $thread2->users);
$user1 = User::factory()->create();
$user2 = User::factory()->create();
$user3 = User::factory()->create();
$user4 = User::factory()->create();
MessageThreadParticapant::factory()->create([
'user_id' => $user1->id,
'thread_id' => $thread1->id
]);
MessageThreadParticapant::factory()->create([
'user_id' => $user2->id,
'thread_id' => $thread1->id
]);
MessageThreadParticapant::factory()->create([
'user_id' => $user2->id,
'thread_id' => $thread2->id
]);
MessageThreadParticapant::factory()->create([
'user_id' => $user3->id,
'thread_id' => $thread2->id
]);
MessageThreadParticapant::factory()->create([
'user_id' => $user4->id,
'thread_id' => $thread2->id
]);
//PASSES!!!!
$this->assertCount(2, MessageThreadParticapant::where('thread_id', $thread1->id)->get());
//FAILS!!!
$this->assertCount(2, $thread1->users);
$this->assertCount(3, $thread2->users);
}
At bottom of my test:
//PASSES!!!!
$this->assertCount(2, MessageThreadParticapant::where('thread_id', $thread1->id)->get());
//FAILS!!!
$this->assertCount(2, $thread1->users);
In other tests, $thread->users works correctly in getting the right amount of users back. Why are these getting different results?
I solved this problem in two steps.
Refresh
Lazy loading apparently only represents that state of the object at the time that it was loaded. Meaning it's not retrieving new data from the DB when the joined property is called. To solve, just do a refresh on the model and then access the joined property.
$model->refresh();
$model->users;
String ID
I'm using UUID in Postegresql. Even those I am using $cast = ['id' => 'string']; in model, this is not enough. I also have to add:
protected $keyType = 'string';

Laravel validation rule fails when trying to update the same record with no changes

I've got a table called Sides which consists of id, name, side_category_id and some other fields not important at the moment.
I wanted to validate that when creating a new side record, the record doesn't exist already. So, let's say I've got in the database a record such as:
id: 1
name: Salad
side_category_id: 3
If I try to insert a new record with name = 'salad' and side_category_id = 3 then the creation must fail and return an error.
I've achieved this by using the following rule:
$rules = [
'name' => 'required',
'side_category_id' => 'required|exists:side_categories,id|unique:sides,side_category_id,NULL,id,name,' . $this->request->get('name')
]
So far so good. It works as it's supposed to. But now it's returning an error if I want to edit a record and save it without any modifications and this is not my desired outcome.
If I try to update the record with no modifications it should succeed. How can I update my rule to achieve this?
you can use Rule::unique()
for create use like this
$rules = [
'name' => ['required'],
'side_category_id' => ['required',Rule::unique('sides', 'name')->where(function ($query) use($category_id) {
return $query->where('side_category_id', $category_id);
}),Rule::exists('side_categories')]
]
for update
$rules = [
'name' => ['required'],
'side_category_id' => ['required',Rule::unique('sides', 'name')->where(function ($query) use($category_id) {
return $query->where('side_category_id', $category_id);
})->ignore($id),Rule::exists('side_categories')]
]
//$id should be you parameter

Return only certain data in from a collection - Laravel

I'm learning Laravel and have created a public endpoint where I want to output only certain information of some comments if a user is not authenticated from a GET request.
I have managed to filter out the comments based on whether or not they are approved. I now want to filter out the data that is returned. I have attached a screenshot of what is currently returned.
Ideally, I only want to return the id, name and the body in the json. How can I go about this? I tried the pluck() method which did not give the desired results. Any pointers would be greatly appreciated
public function index(Request $request)
{
if (Auth::guard('api')->check()) {
return Comment::all();
} else {
$comments = Comment::where('approved', 1)->get();
return $comments->pluck('id','name','body');
}
}
To select the particular columns, you can pass columns name to get as
$comments = Comment::where('approved', 1) -> get(['id','name','body']);
You can use a transformer to map the incoming data to a sensible output based on the auth state. The following example comes from the Fractal lib:
<?php
use Acme\Model\Book;
use League\Fractal;
$books = Book::all();
$resource = new Fractal\Resource\Collection($books, function(Book $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
'author' => [
'name' => $book->author_name,
'email' => $book->author_email,
],
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
]
];
});
Ideally, you would create 2 classes that extend from Transformer and pass the correct one to the output.
If you want to pass the result as json respose
$comments = Comment::where('approved', 1)->pluck('id','name','body')->toArray();
return Response::json($comments);
If you want to pass the result as to blade
$comments = Comment::where('approved', 1)->pluck('id','name','body')->toArray();
return view('your_blade_name')->with('comments',$comments);

CakePHP HABTM Save Single Header Record, linked against multiple existing children

I am using CakePHP 2.9.1, have 2 tables in a HABTM relationship, I want to save the header record and save many associations in one go, but the child record is already existing, I seem to be able to save the data using a hardcoded list but not using a variable, I'm sure it's something silly I'm doing, I'm running inside a plugin, the model I want to link too is in the main code.
The child record must not be altered because it's handled by the main system, I'm just extending it's functionality by linking to it in our plugin.
// Header Record
class templatedoc extends TemplateModuleAppModel
{
public $useTable = 'templatedocs';
public $hasAndBelongsToMany = [
'Servicetemplate' => [
'className' => 'Servicetemplate',
'joinTable' => 'templatedocs_to_servicetemplates',
'foreignKey' => 'templatedoc_id',
'associationForeignKey' => 'servicetemplate_id',
'unique' => true,
'dependent' => false, // We don't want to delete the Service Template by mistake!
]
];
}
Here is my save, this works:
$this->templatedoc->create ();
$data = [
'templatedoc' => [
'warning_adjust' => $prioritywarn,
'summary' => $summary,
],
'Servicetemplate' => [
1,2,3,10 // Dynamic data needed here!
]
];
$result = $this->templatedoc->SaveAll ($data);
But I can't have the "Servicetemplate" hardcoded, I've tried passing an array inside that array and removing it, imploding an array to make a comma separated list all I end up with is the header 'templatedoc' record being created but nothing in my link table
Only thing I can think is a difference is that the hardcoded list are int values, by data is coming from the database so will be an int inside a string, but I can't see that being an issue.
Any ideas what I'm doing wrong? Sorry if it's something completely stupid.
What I had to do in the end was store the items forcing an int, when associating in my foreach loop.
$items[] = (int) $id
Then this worked:
$this->templatedoc->create ();
$data = [
'templatedoc' => [
'warning_adjust' => $prioritywarn,
'summary' => $summary,
],
'Servicetemplate' => [
$items // Dynamic data needed here!
]
];
$result = $this->templatedoc->SaveAll ($data);
Seems very fussy it has to be type int, adding here in case someone else gets caught out, would love to know if there is a better solution.

Categories