Welcome Guest, Not a member yet? Register   Sign In
Using Preloaded Model Via BaseController in Other Controllers
#1

Hi Everyone,

I am trying to use and/or extend a model which is loaded in the BaseController's initController() function which uses the RequestInterface.

I was using the below to set a variable in initController().
PHP Code:
new \App\Models\Base_layout_markup_model$request ); 

I wish to use this model globally, and have other models extend from it, so I can call its functions in the other model, however, nothing has come of it.  I get errors mentioning I need to set database properties, to references of NULL when calling the base model functions in the class that extends it.

The only way I get things to work is using this process:
  • A controller, (extends BaseController), has a constructor which loads a model for that Controller, passing in the services I need.
    PHP Code:
    new \App\Models\Site_home_model( \Config\Services::request() ); 
  • The Site_home_model extends the Base_layout_markup_model, which also has the using App\Models\Base_layout_markup_model;.  Then in this model I have to use a constructor to call the Base_layout_markup_model this way:
    PHP Code:
    public function __constructor$request )
    {
    $this->_base_layout_markup_mdl = new \App\Models\Base_layout_markup_model$request ); 

    so that I can call its functions.
I would have thought that since I am extending from that 'base' model, I would not have to instantiate it in the constructor?

I also thought the idea of preloading models in the BaseController would make them available globally?  Otherwise, each model that I load then has to load the base layout model if I want to use it.

What is the best practice of preloading a model in the BaseController, which has access to the RequestInterface, which is available globally in other models?
Reply
#2

Quote:I would have thought that since I am extending from that 'base' model, I would not have to instantiate it in the constructor?

You don’t need to instantiate it if you extend the class. Show some more code, you’re doing something wrong.

I’m not sure what you’re trying to do, but you can load a shared instance of a model with the model() function. You don’t need to load it globally in the base controller in case you need it.

Also, the controller already has the request object in $this->request. You don’t need to load it.
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
Reply
#3

Hi, more code.

The intent is to have a model for each controller if really need specific data for that page.

The base layout view will be driven by the 'base layout markup' model, where the 'controller specific' models will call its '_base_layout_markup' function.


My BaseController:

PHP Code:
namespace App\Controllers;

use 
CodeIgniter\Controller;
use 
CodeIgniter\HTTP\CLIRequest;
use 
CodeIgniter\HTTP\IncomingRequest;
use 
CodeIgniter\HTTP\RequestInterface;
use 
CodeIgniter\HTTP\ResponseInterface;
use 
Psr\Log\LoggerInterface;

/**
 * Class BaseController
 *
 * BaseController provides a convenient place for loading components
 * and performing functions that are needed by all your controllers.
 * Extend this class in any new controllers:
 *     class Home extends BaseController
 *
 * For security be sure to declare any new methods as protected or private.
 */
class BaseController extends Controller
{
  /**
   * Instance of the main Request object.
   *
   * @var CLIRequest|IncomingRequest
   */
  protected $request;

  /**
   * An array of helpers to be loaded automatically upon
   * class instantiation. These helpers will be available
   * to all other controllers that extend BaseController.
   *
   * @var array
   */
  protected $helpers = [
  'Common_functions'
];

  /**
   * Constructor.
   */
  public function initController(RequestInterface $requestResponseInterface $responseLoggerInterface $logger)
  {
    // Do Not Edit This Line
    parent::initController($request$response$logger);

    // Preload any models, libraries, etc, here.

    // E.g.: $this->session = \Config\Services::session();
    $this->session = \Config\Services::session();

    // The default DB connection.
    $this->db = \Config\Database::connect();

    // The site's layout view markup model, available to all controllers which
    // is extended from this BaseController.  Shared instance of the model.
    $this->base_layout_markup_mdl model'App\Models\Base_layout_markup_model' );
  

The site home controller:

PHP Code:
namespace App\Controllers;

/**
* Class Site_home
*
* Site_home is the class for the site's home page.
*/
class Site_home extends BaseController
{
  /**
* The Site Home Model

* @var Object
*/
private $site_home_mdl;

/**
* The Constructor.
*/
public function __construct()
{
// Shared instance of the model.
$this->site_home_mdl model'App\Models\Site_home_model' );
}

/**
* Site Home Index page.

* @param NIL

* @return PageView
*/
  public function index()
  {
    // Obtain Site Home Index Data.
    $data $this->site_home_mdl->_index();


    // Return the view.
    return view'site/site_index_body'$data );
  

The site home model:

PHP Code:
namespace App\Models;


/**
 * Site Home model
 * 
 * The Site_home_model obtains the data for the site's home page.
 */
class Site_home_model extends Base_layout_markup_model
{
 
/**
 * The Constructor.
 */
 
public function __construct()
 {
 
 }

 
/**
 * Site Home Index page.
 * 
 * Obtain and return an array of date for site home index page.
 * @param NIL
 * 
 * @return array
 */
  public function _index() : array
  {
    $base_markup $this->_base_layout_markup();

    $data['base_layout_markup'] = $base_markup;


    return $data;
  

The base layout markup model:

PHP Code:
namespace App\Models;

use 
CodeIgniter\Model;

/**
 * Base layout markup info model
 * 
 * The Base_layout_markup_info_model has functions which obtain the site's
 * layout view markup.
 */
class Base_layout_markup_model extends Model
{
 protected 
$table      'page_markup_info';
  protected $primaryKey 'page_markup_info_id';

  /**
  * Instance of the main Request object.
  *
  * @var CLIRequest|IncomingRequest
  */
  private $_request;

 
/**
 * The requested URI Object.
 * 
 * @var Object
 */
 
private $_uri_obj;

 
/**
 * The number of URI segments.
 * 
 * @var Integer
 */
 
private $_nos_uri_segments;

 
/**
 * The requested URI cast to a string.
 * 
 * @var String
 */
 
private $_uri_str;

 
/**
 * Segment One of the URI.
 * 
 * @var String
 */
 
private $_segment_one;

 
/**
 * Segment Two of the URI, defaults to NULL.
 * 
 * @var String
 */
 
private $_segment_two NULL;

 
/**
 * Segment Three of the URI, defaults to NULL.
 * 
 * @var String
 */
 
private $_segment_three NULL;

 
/**
 * The Constructor.
 */
 
public function __construct()
 {

 }

 
/**
 * Base Layout Markup.
 * 
 * Obtain and return an array of markup for the base layout view.
 * @param NIL
 * 
 * @return array
 */
 
public function _base_layout_markup$request ) : array
  {
 
// The request.
 
$this->_request $request;

 
// The URI Object.
 
$this->_uri_obj $this->_request->uri;

 
// The number of URI segments.
 
$this->_nos_uri_segments $this->_uri_obj->getTotalSegments();

 
// The URI, cast to a string.
 
$this->_uri_str = (string)$this->_request->uri

I am now at the point where I receive Attempt to read property "uri" on null on the following line:

Code:
$this->_uri_obj = $this->_request->uri;

The only way around this is by replacing:

Code:
$this->_request = $request;
with the following and removing the passed in $request variable:
Code:
$this->_request = \Config\Services::request();

Is that considered a 'best practice'?
Reply
#4

(10-10-2021, 11:43 PM)Mr Lister Wrote: I am now at the point where I receive Attempt to read property "uri" on null on the following line:

Code:
$this->_uri_obj = $this->_request->uri;

The only way around this is by replacing:

Code:
$this->_request = $request;
with the following and removing the passed in $request variable:
Code:
$this->_request = \Config\Services::request();

You have this error because you never pass the request object to your model, $request is null:
PHP Code:
$base_markup $this->_base_layout_markup(); // nothing is passed for the $request parameter 

A model is tied to a database table. Yours seemed to be tied to a specific controller, which is weird in my opinion.
I’m not sure either why your model need to access the request object. If you follow the MVC pattern, this would be a job for the controller. The models are used for database operation, not dealing with the request and user input.
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
Reply
#5

@includebeer My idea is to have a shared model, (the 'base layout markup'), which will set up the layout view and the renderSection() content is obtained via the specific controller.

When using the extend() function in the content, the layout view inherits the data.

The controller calls its model, (which will most likely have a db table), this model will extend the shared 'base layout markup', so has access to its functions to gather the layout view data.

Ultimately the 'base layout model' and its associated table will have meta data, HTML title tags etc to create the page, which will be determined by the URI segments, i.e., domain.com/segment-one/segment-two will query the db table for 'segment-one' and 'segment-two' for the page layout.  Hence why I need the request service so I can obtain the URI segments.

What is the best way to obtain the URI segments in the model?  Or should I use the controller to pass $this->request to its model, which then passes it to the 'base layout markup' model?

My strategy is to have a db table to contain meta data, HTML tag data to generate the layout view, and then its associated content can be in its own table.  This can then be edited / updated etc in an Admin area.

Alternatively, all of the page's markup and content will be in one table, (and not use the layout view / renderSection() function), which will be queried using the URI segments, which more or less leaves me with the same issue, obtaining the URI segments.  But I would like to separate those concerns; update one layout view rather than have to update it for each view for a controller if it requires update.

Hope that makes sense for my thinking :-)

I love the idea of Codeigniter allowing developers to do it 'their way' in some respects.  However, as you mention, I also wish to stick to the MVC pattern as much as possible.
Reply
#6

Change this in your base layout model.

PHP Code:
// Change: The request.
$this->_request $request;

// To: The request.
$this->_request $request = \Config\Services::request(); 

Try that.
What did you Try? What did you Get? What did you Expect?

Joined CodeIgniter Community 2009.  ( Skype: insitfx )
Reply
#7

Personally I would collect what I need from the request in the controller, and pass this to the model as a parameter. This way the model is not dependent on the incoming request and only deal with database requests. It would also make it easier to test your model if you can just pass an array of options.

But like you said, CI doesn’t force anything and let you do what you want, how you want. So it’s up to you. But I advise to stay as close as possible to the MVC pattern.
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
Reply
#8

@InsiteFX, @includebeer

Thank you for your replies and help.  I think I will split the difference in what I will implement.

In my BaseController, I will obtain the current url as an object instance via the URL Helper, as it is automatically loaded on every request.

PHP Code:
$this->_url_request current_urltrue ); 

I then pass that parameter to the model(s) from the controller.  If that is in-keeping with MCV patterns, I am happy to go with it.

Thanks again, much appreciated.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB