• 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Loading a model without instantiating (OOP support in CI)

#1
[eluser]beardedlinuxgeek[/eluser]
I think this a pretty common situation. You have a model class and some other classes that extend that class. In this case we have a few different types of vehicles, each with an ID in the database.

Code:
class Vehicle extends CI_Model {

var $id;
var $model;
var $color;


function __construct ( $vehicle_id ) {
  parent::__construct();

  $vehicle = $this->db->get_where( 'vehicle', array('id' => $vehicle_id) )->row();
  foreach ( $vehicle as $property => $value ) {
   $this->$property = $value;
  }
}


function schedule maintenance ( $date ) {
  // Schedule maintenance
}

}

class Car extends Vehicle {

function drive () {
  // Drive the car
}

}


class Helicopter extends Vehicle {

function fly () {
  // Fly the helicopter
}

}

I want to create my car I can just do
Code:
$car5 = new Car(5);
and my code is going to connect to the database and load up all the details for that car.

But when I do
Code:
$this->load->model('vehicle');
in the controller I get an error (obviously) for not having enough parameters because it loads the vehicle class into the CI super class without an id.

I could do something like
Code:
function __construct ( $vehicle_id = null ) {
if ( empty($vehicle_id) ) return;
but it feels clunky and hackish and I know for a fact I'll never call the model directly because
Code:
$this->vehicle->schedule_maintenance('Friday');
doesn't make sense without having created a vehicle object.

One thing I was thinking of doing is creating a new folder called "objects" and not having the Vehicle class extend CI_Model at all. But then I'd need to create a new function like
Code:
function set_db( $db ) {
$this->db = $db;
}

and then create my car from the controller like
Code:
$car5 = new Car(5);
$car5->set_db( $this->db );

which really isn't a bad idea from an Inversion of Control perspective. But I also only have one database and only plan on having one, so the benefit from this IoC is really minimal. And let say I decide I need to send an email to the guy doing the maintenance, now I need to load the email class and do
Code:
$this->load->library('email');

$car5 = new Car(5);
$car5->set_db( $this->db );
$car5->set_email_handler( $this->email );
and you can see how this gets out of hand quickly. It's great from an IoC perspective, but I just feel like managing dependency injection of the core CodeIgniter classes isn't really that useful.

Advice?

#2
[eluser]CroNiX[/eluser]
You might try to manually include the model, instead of using CI's loader which automatically instantiates it. I haven't tried it, though.

Code:
include_once(APPPATH . 'models/your_model.php');
$something = new Your_model();

#3
[eluser]beardedlinuxgeek[/eluser]
[quote author="CroNiX" date="1348099577"]You might try to manually include the model, instead of using CI's loader which automatically instantiates it. I haven't tried it, though.[/quote]

When you call a model from a controller using this method it doesn't end up extending CI_Model. I'm not sure why, I didn't look into it.

I could also do
Code:
class DoNothingModel extends CI_Model {
  function __construct ( $vehicle_id ) {
    parent::__construct();
  }
}

class Vehicle extends DoNothingModel {
  function __construct ( $vehicle_id ) {
    parent::__construct();
  }
}

But that also seems hackish.

I know you can extend core classes, but I've only tried it with CI_Controller. I wonder what results you get if you try it with CI_Model. I'll have to try when I'm at my desktop tomorrow.

I figured that creating a normal object class outside CI_Controller and CI_Model but letting it use the core classes/libraries would be pretty standard.


I also had one more idea. I could do
Code:
class Vehicle {

  var $db;

  function __construct ( $vehicle_id ) {
    require_once(BASEPATH.'database/DB'.EXT);
    $this->db = DB();
  }

}
All the advantages of having an object independent of the Controller/Model CI superclass, but also database access without having to write $vehicle->set_db() every time.

And at that point you might as well create a static "CreateObject" class which handles the dependency injection so you get all the benefits.

Code:
class CreateObject {

  public static function car( $car_id ) {
    
    require_once(APPPATH.'objects/vehicle'.EXT);

    require_once(BASEPATH.'database/DB'.EXT);
    $db = DB();

    $car5 = new Car( $vehicle_id );
    $car5->set_db($db);
    return $car5;
  }

  public static function train( $train_id ) {
    // you get the idea
  }

}
and in your controller
Code:
// Single line object creation
$car5 = CreateObject::car(5);

// Ability to change database in the future (even if you never will)
$car6 = new Car(6);
$car6->set_db( $another_db );

This looks like a decent solution to me. Anyone see problems with it?

#4
[eluser]alexwenzel[/eluser]
You can override the Loader. Check out this Code. I do exactly the thing you want there:

https://bitbucket.org/alexwenzel/codeign...Loader.php

#5
[eluser]beardedlinuxgeek[/eluser]
[quote author="alexwenzel" date="1348136028"]You can override the Loader. Check out this Code. I do exactly the thing you want there:

https://bitbucket.org/alexwenzel/codeign...Loader.php[/quote]

Works perfectly.

I have to wonder why this isn't the default behavior. It makes more sense to treat models as objects and if you need the current behavior where you use a model statically, why not just declare a static function?

Can someone explain why it's the way it is? The engineers at EllisLabs are pretty smart guys, so I'm sure I'm just misunderstanding something.

#6
[eluser]alexwenzel[/eluser]
[quote author="beardedlinuxgeek" date="1348153614"][quote author="alexwenzel" date="1348136028"]You can override the Loader. Check out this Code. I do exactly the thing you want there:

https://bitbucket.org/alexwenzel/codeign...Loader.php[/quote]

Works perfectly.

I have to wonder why this isn't the default behavior. It makes more sense to treat models as objects and if you need the current behavior where you use a model statically, why not just declare a static function?

Can someone explain why it's the way it is? The engineers at EllisLabs are pretty smart guys, so I'm sure I'm just misunderstanding something.[/quote]

I guess its because they attach every model per default to the codeigniter class, so you can call them via:

Code:
$this->model->something();

#7
[eluser]beardedlinuxgeek[/eluser]
[quote author="alexwenzel" date="1348155244"]
I guess its because they attach every model per default to the codeigniter class, so you can call them via:

Code:
$this->model->something();
[/quote]

Yeah, but why do it like that? Your way gives us more control over our models.

The only reason not to use your way is when you want to use the functions in your model statically. In which case writing
Code:
Model::something();
gives you the benefit of code autocomplete in your IDE. I also have all these PHPDoc statements like
Code:
/**
* Get user login
*
* @param int The user's id
* @return string The user's login name
*/
function get_userlogin( $user_id ) {
but when I hover over the line
Code:
$this->user->get_userlogin();
I get no information. Every time you use a function where you don't remember the parameters you have to go scrolling through your editor to look it up.

Sorry if this sounds like complaining, it's not meant to be. I just see a ton of reasons to load models like this and not a single reason not to.

#8
[eluser]alexwenzel[/eluser]
If you like you can checkout my whole model override. Its currently in version 1.0 but i will release the next day next version which brings a lot more comfort with.

Simple Model for CodeIgniter:

forum: http://ellislab.com/forums/viewthread/223845/

code: https://bitbucket.org/alexwenzel/codeigniter-model/


Digg   Delicious   Reddit   Facebook   Twitter   StumbleUpon  


  Theme © 2014 iAndrew  
Powered By MyBB, © 2002-2020 MyBB Group.