Welcome Guest, Not a member yet? Register   Sign In
Insert in joint table before inserting in another one
#1

Hi,
I have a model for bookings linked to clients (it uses clients.id to join to the clients table).
I am working on an admin form to create new bookings. I use an autocomplete on the client name, that allows the admin to select an existing client from the clients table.
When a client exists already and the user clicks on a suggestion from the autocomplete, I send its client_id as value to a hidden field that will be used in the insert for the new booking.
That works perfectly, but I am struggling now with the scenario when there is no existing client, so the user types in the new client name, the client_id field is empty, and when I post the form, I need to first create the client, get its new id, and then create the booking using that new client_id.

I remember seeing something in the docs related to before/after actions, and I am thinking that maybe I should have in this case a createClient function that has a callback to the createBooking one. But I can't figure it out.
Can someone point me to the right direction?
Reply
#2

You do as you wish. First you need to create a client, but what data will you fill in? Out of shape? The main thing is not to forget to delete the client if the record could not be created
Simple CI 4 project for beginners codeigniter-expenses ( topic )
Reply
#3

(This post was last modified: 05-09-2024, 07:23 AM by kcs.)

Yes, I know i need to first create the client, my question is related to how you organise your code to do 2 actions that follow each other, concerning 2 different models.
(For each client I have just a name and an ID at the moment)

My current situation does this in my BookingsController:
PHP Code:
public function create()
    {

        $booking = new Booking($this->request->getPost());
        $id $this->model->insert($booking);

        // if insert failed, we redirect to the form with errors and input data
        if ($id === false) {
            return redirect()->back()
                ->with("errors"$this->model->errors())
                ->withInput();
        }

        return redirect()->to('admin/bookings/')
            ->with("message""Booking added to the database.");
    

Should I add the client creation inside that function, calling the clients model? Is that the right place to do it?
Reply
#4

No. You need to create a separate layer - BookingManager. You can already create a client in it and return boolean or data.

PHP Code:
$service = new BookingManager();
$result $service->createBookingByClient($booking$client_id);
// or set null, if client not exists
// may be return errors
$service->getErrors(); 

In the future, you will not have to change the handler in the controller, but simply execute it in the service
Simple CI 4 project for beginners codeigniter-expenses ( topic )
Reply
#5

(This post was last modified: 05-09-2024, 11:35 PM by kcs.)

I see. I have never created a service, i’ll try that. Thanks a lot for your help

// after checking documentation and some articles
If i understand the benefits of using a service, #1 being able to call one instance that is the same wherever we call it, I feel that is not what I need. I might not have been clear about that client id: it can change at each booking the admin will create. In one session, potentially the admin creates 10 bookings to update the agenda, each of them will have a different client_id, and maybe several ones will be a new client.

In my head, so far i thought one controller = one model. But i think i can use two models inside the same controller?
So in my create function for a booking, that means that before i do the insert for the booking, i should create the new client that will give me the new ID back to use in the insert booking.

Am I thinking this correctly?
Is that what the beforeInsert events are for? Is that the way I should go for?
Reply
#6

heh. You're not listening to me. You can try any method. But you ask again how to do it. So that's what you want - create an event, no problem.
The only difference is who performs the action - the model or you in the controller (service). The main process remains the same, it is to create customers.
You can insert the same service into the event. Only the input data will be slightly different (for SQL queries)
Try to abstract yourself first. Write a working code based on $booking and $client_id (or an array of clients). Further complicate the process and you're done!
Simple CI 4 project for beginners codeigniter-expenses ( topic )
Reply
#7

(05-10-2024, 12:11 AM)ozornick Wrote: heh. You're not listening to me. You can try any method. But you ask again how to do it. So that's what you want - create an event, no problem.
The only difference is who performs the action - the model or you in the controller (service). The main process remains the same, it is to create customers.
You can insert the same service into the event. Only the input data will be slightly different (for SQL queries)
Try to abstract yourself first. Write a working code based on $booking and $client_id (or an array of clients). Further complicate the process and you're done!

Maybe not understanding what seems obvious to you is more accurate?

I am sharing my thinking because I am struggling to understand both what you are suggesting and what I can read from the documentation and tutorials I spent an hour on before coming back here.

So am I listening? Yes. Am I understanding? no. That's why I am asking for help and feedback on my thinking.
Reply
#8

See example https://github.com/neznaika0/codeigniter...penses.php
Inside you can add additional logic
Simple CI 4 project for beginners codeigniter-expenses ( topic )
Reply
#9

(This post was last modified: 05-11-2024, 05:39 AM by kcs.)

Thanks, the full example is very useful so I can navigate files and try to understand how everything works. Also gave me a good suggestion to rename the ->model I was using in ->modelName which makes it so much clearer when reading the code, to know which model you are dealing with  Tongue  

I am trying to follow your example, but running into errors I cannot quite make sense of. Here's what I did:
- i created the Clients service inside the services folder. It looks like this:
PHP Code:
<?php

namespace App\Services;
use 
App\Entities\Client;
use 
App\Models\ClientsModel;
use 
CodeIgniter\Model;

class 
Clients
{
    protected Model $entityModel;

    public function __construct()
    {
        $this->entityModel model(ClientsModel::class);
    }

    public function create(array $properties): ?Clients
    
{
        $properties $this->entityModel->filterAllowedFields($properties);

        $client  = new Client($properties);
        $insertID = (int) $this->entityModel->insert($client);

        // Set new ID
        if ($insertID 0) {
            $client->id $insertID;

            return $client;
        }

        return null;
    }


- the clientsModel looks like this:
PHP Code:
<?php
namespace App\Models;
use 
CodeIgniter\Model;

class 
ClientsModel extends Model
{
    protected $table            'clients';
    protected $primaryKey      'id';
    protected $useAutoIncrement true;
    protected $allowedFields    = ['id','client_name'];

    protected $validationRules = [
        'client_name' => 'required'
    ];


- and I added into app/Config/Services.php
PHP Code:
class Services extends BaseService
{
    public static function clients($getShared true): Clients
    
{
        if ($getShared) {
            return static::getSharedInstance('clients');
        }

        return new Clients();
    }


- in my BookingsController,  I am for now just 'adding' the calls to the service, and the biggest changes I had to make are in the construct part. I am not sure what I am doing there so not entirely sure what I am not doing correctly.

PHP Code:
<?php
namespace App\Controllers\Admin;
use 
App\Controllers\BaseController;
use 
App\Models\BookingModel;
use 
App\Entities\Booking;
use 
App\Entities\Client;
use 
App\Models\ClientsModel;
use 
App\Services\Clients;

use 
CodeIgniter\Exceptions\PageNotFoundException;
use 
CodeIgniter\HTTP\RedirectResponse;
use 
CodeIgniter\HTTP\RequestInterface;
use 
CodeIgniter\HTTP\ResponseInterface;
use 
Psr\Log\LoggerInterface;


$pager = \Config\Services::pager();

class 
BookingsController extends BaseController
{
    // added this to call the service
    private Clients $clientsService;
    public BookingModel $bookingModel;

    // and added this to follow your example
    public function initController(RequestInterface $requestResponseInterface $responseLoggerInterface $logger)
    {
        parent::initController($request$response$logger);
        $this->bookingModel = new BookingModel(BookingModel::class);
        $this->clientsService service('clients');
    }

    public function index(): string
    
{

        $today date('Y-m-d');

        $data = [
            'bookings' => $this->bookingModel->where('date >='$today)
                ->orderBy('date ASC, start_time ASC')
                ->join('presentations''presentations_id = presentations.id''inner')
                ->join('clients''clients_id = clients.id''inner')
                ->paginate(30),
            'pager' => $this->bookingModel->pager,

        ];

        $this->viewData['bookings'] = $data['bookings'];
        $this->viewData['pager'] = $data['pager'];

        return view("admin/bookings/index"$this->viewData);
    }

    public function new() {
        $this->viewData['booking'] = new Booking();
        return view('admin/bookings/new'$this->viewData);
    }

    public function create()
    {

        $bookingModel = new Booking($this->request->getPost());

        // create new client if needed before?


        $id $this->model->insert($booking);

        // if insert failed, we redirect to the form with errors and input data
        if ($id === false) {
            return redirect()->back()
                ->with("errors"$this->model->errors())
                ->withInput();
        }

        //otherwise we redirect to the bookings view
        return redirect()->to('admin/bookings/')
            ->with("message""Booking added to the database.");
    }
    public function edit($id) {
        $this->viewData['booking'] = $this->model->where('bookings.id'$id)
            ->join('presentations''presentations_id = presentations.id''inner')
            ->join('clients''clients_id = clients.id''inner')
            ->first();;
       return view('admin/bookings/edit'$this->viewData);
    }

But that broke everything, when I try to access bookings now, I get the error 
Code:
TypeError
CodeIgniter\Model::__construct(): Argument #1 ($db) must be of type ?CodeIgniter\Database\ConnectionInterface, string given, called in /app/Controllers/Admin/BookingsController.php on line 28

which is the line 
Code:
$this->bookingModel = new BookingModel(BookingModel::class);
So I am doing something wrong, I just have no idea what  Confused


// Edit
found that changing
Code:
$this->bookingModel = new BookingModel(BookingModel::class); 
into 
Code:
$this->bookingModel = new BookingModel();

solved things for now, so I am moving on to try to make the creation of the new client work.
Reply
#10

I am making progress, from error to error. Managed to solve a bunch (I have plenty of questions though, but for now I am focusing on making things work).
I modified my create function and can see that a new client is added to the database when I call the service  Cool but I am getting an error on the return type, and I have no clue how to solve it.

Here's the revised code:
Code:
    public function create()
    {
        // using the entity, it falls back to null values
        // if there are no post data
        $bookingModel = new Booking($this->request->getPost());


        // create new client if needed
        if(isEmpty($bookingModel->client_id)) {
            $this->clientsService->create(['client_name' => $bookingModel->client_name]);
        }
        ...

And the service
PHP Code:
namespace App\Services;

use 
App\Entities\Client;
use 
App\Models\ClientsModel;
use 
CodeIgniter\Model;

class 
Clients
{
    protected Model $entityModel;

    public function __construct()
    {
        $this->entityModel model(ClientsModel::class);

    }

    public function create(array $properties): ?Clients
    
{
        $properties $this->entityModel->filterAllowedFields($properties);

        $client  = new Client($properties);
        $insertID = (int) $this->entityModel->insert($client);

        // Set new client ID
        if ($insertID 0) {
            $client->id $insertID;

            // if I check, I get the new client including its ID.
            // and the record is inserted in the clients table hurray :D
            // dd($client);

            // but I get a return type error
            // Return value must be of type ?App\Services\Clients, App\Entities\Client returned
            // how to change the return type?
            return $client;
        }

        return null;
    }



How do I make the return to solve this type error?  Huh
Reply




Theme © iAndrew 2016 - Forum software by © MyBB