Cannot get DataMapper to work in CodeIgniter - php

I'm trying to implement an ORM in a CodeIgniter application, but cannot get it to work. To start I'm just trying to instantiate a simple test model:
<?php
class Cart extends DataMapper
{
public function __construct()
{
// model constructor
parent::__construct();
}
var $validation = array(
'username' => array(
'label' => 'UserName',
'rules' => array('required', 'trim', 'unique', 'alpha_dash', 'min_length' => 1, 'max_length' => 50),
)
);
}
?>
And then in the Controller I try this:
public function __construct()
{
parent::__construct();
$this->load->model('cart');
}
public function index()
{
$cart = new Cart();
}
But I don't even get past the constructor. The debugger stops and gives me a message saying "Waiting for an incoming connection with ide key xxxxx" (random number)
BTW the cart model class file name is in lower case, but the class name in upper case. I tried both in the constructor.
I have followed the instructions for installation carefully, copying the two datamapper files to libraries and config folders, as well as autoloading the datamapper library.
But it just doesn't work. Am I missing something? The table I'm trying to map is only a test table that actually only has an id and a username field. I don't actually understand the validation array, but just followed the examples in the docs and modified to my field. The id field doesn't seem like anyone has put in the validation array.
I should also mention that I'm a newbie at CodeIgniter.

Your code seems mostly correct for use with DataMapper ORM and CodeIgniter.
To explain things a bit, DataMapper is just an abstraction layer. It handles a lot of the necessities when working with databases and mapping your objects to your tables. That being said, you don't have to load your models, etc. As long as you are autoloading your database library and datamapper library, you can use DataMapper.
The validation array lets DataMapper know the requirements to your properties. So, if you try to save an object and one of the properties that you've created/changed doesn't meet those requirements, then your save will fail and you'll get an error message:
// For example
if ($myObj->save())
{
// $myObj validation passed and is saved to db
}
else
{
// $myObj validation failed, save did not complete
echo $myObj->error->string;
}
Codeigniter already has a library named Cart, so you wouldn't want to name your model Cart. So you could rename that model to Basket or something else that makes sense.
I know you're still just trying to get things to work, but I feel you need to think about your data structure a bit. You wouldn't save the username in the Cart object, that's why we use relations. So, I would structure it a bit like this:
// baskets table (a table represents many baskets, therefore it is plural)
id
user_id
blah
blah
created
updated
// users table
id
username
email_address
created
updated
// basket model (a model represents 1 basket, therefore it is singular)
class Basket extends DataMapper
{
public function __construct()
{
parent::__construct();
}
var $has_one = array('user'); // each basket belongs to one user
var $validation = array(...);
}
// user model
class User extends DataMapper
{
public function __construct()
{
parent::__construct();
}
var $has_many = array('basket'); // each user can have many baskets
var $validation = array(...);
}
// controller
public function __construct()
{
parent::__construct();
}
public function index()
{
$basket = new Basket();
$basket->blah = 'whatever';
$basket->save();
// at this point, $basket is saved to the database
// now let's add it to the user
$user = new User();
$user->where('id', 1)->get(1);
// now we have a user
// save the relationship to the basket
$user->save($basket);
// now $basket->user_id == 1
// get the username from the basket
$u = $basket->user->get();
$username = $u->username;
// yes, there are faster and shorter ways to write most of this,
// but I think for beginners, this syntax is easier to understand
}

The CodeIgniter documentation about models states that you can load a model by calling
$this->load->model('Model_name');
in the constructor, and that you can access this model in your controller by doing
$this->Model_name->function();
So you should change your Controller code into
public function __construct()
{
parent::__construct();
$this->load->model('Cart');
}
public function index()
{
$this->Cart->functionCall();
}

Related

Assignment in model constructor does not work

I have a car with two columns: user_id and token
I would like to only pass the user_id on creation and create a token automatically:
$car = Car::create([
'user_id' => $user->id,
]);
this is my car class:
class Car extends Model
{
protected $guarded = [];
public function __construct()
{
parent::__construct();
$this->token = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
}
When I create a car, the token field is correctly inserted. However the user_id field is null.
When I remove the __construct() method, then the user_id is correctly inserted (but then there is ofc no token).
I don't understand why the assignment in the constructor removes the user_id.
Any suggestions?
Rather than creating the token in your constructor, you could take advantage of Laravel's model events. In short, this allows you to listen for an event (e.g. "created," "updated," etc.), and perform an action on that event. If you replace your constructor with the following, it should solve the issue:
public static function boot()
{
self::created(function ($model) {
$model->update([
'token' = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
]);
});
// If you're using the SoftDeletes trait, uncomment this line.
// static::bootSoftDeletes();
}
You'll create an instance of your Car model in your controller, and then the model event will update that instance with your token.
As an aside: since the token is generated randomly, and seems not to rely on any other data/functions, I don't believe there's any shame in dropping this line:
'token' = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
Into the create method in your controller. Based on what you've provided, it would be the simplest way to solve for what you need.
The problem is your constructor doesn't have the correct method signature.
The create method in the laravel model creates a new model: $model = new static($attributes); The $attributes array is what sets the data on your fresh model. You need to make sure your constructor takes the attributes argument and passes it to the parent:
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->token = mb_substr(bin2hex(openssl_random_pseudo_bytes(32)),0,8);
}

What is a good way of associating a Model instance with it's own Class in Laravel?

I'm sure there is a common pattern for this kind of thing, and I'm struggling with search terms to find answers, so please bear with me if is this a dupe.
I have a few Classes in my app that create pretty standard Models that are stored in a relational database, eg;
// AtsType::name examples = 'XML', 'RSS', 'SOAP'
class AtsType extends Model
{
public function ats_instances()
{
return $this->hasMany('App\AtsInstance');
}
public function import()
{
}
}
What I need that import() method to do, however, somehow invokes a class/interface/contract/whatever based upon the actual model instance. So something like this;
AtsTypeRss::import()
AtsTypeXml::import()
AtsTypeSoap::import()
I'd like them to be standalone classes, in order to eventually use some artisan commands that will generate them for a developer, along with a data migration to create the new model names into the database.
I'm just unsure how to go about this.
You could try something like (as seen here), I've searched how to use variable in namespace :
class AtsType extends Model
{
protected $import_method = 'MyMethod';
public function ats_instances()
{
return $this->hasMany('App\AtsInstance');
}
public function import()
{
$string = $this->import_method;
$class = '\\controller\\' . $string;
$newObject = new $class();
}
}

How to access one controller action inside another controller action?

I am using cakephp-2.x. I have one function name user_info() in the UsersController.php i want to access this in another controller name MessagesController.php
Code -
UsersController.php
public function user_info(){
$user_id=$this->Session->read('Auth.User.id');
$data=$this->User->findById($user_id);
$this->set('user_info',$data);
}
MessagesController.php
public function index(){
//$userInfo=new UsersController();
//$userInfo->user_info();
$this->user_info();
pr($data);
}
Error Message-
Fatal Error
Error: Call to undefined method MessagesController::user_info()
File: E:\xampp\htdocs\2014\myshowcam\msc\app\Controller\MessagesController.php
Line: 18
Notice: If you want to customize this error message, create app\View\Errors\fatal_error.ctp
Typically if you're trying to access a function in one controller from another controller you have a fundamental flaw in your project's logic.
But in general object usage is thus:
$otherController = new whateverMyControllerNameIs();
$otherController->functionName();
However I'm not familiar enough with cake to tell you the pitfalls of doing such a thing. For example I have no idea what this would do to routes or what other variables/objects are required to initialize a controller correctly.
EDIT:
Ref: CakePHP 2.3.8: Calling Another Controller function in CronController.php
App::import('Controller', 'Products'); // mention at top
// Instantiation // mention within cron function
$Products = new ProductsController;
// Call a method from
$Products->ControllerFunction();
Try requestAction function of cakephp
$result = $this->requestAction(array('controller' => 'users', 'action' => 'user_info'));
Why would a simple, When can complicated?
All the information for a registered user of User model is accessible in the following manner:
AppController.php
public $user_info; /* global scope */
public function beforeFilter(){
$this->user_info = $this->Auth->user(); // for access user data in any controller
$this->set('user_info_view',$this->Auth->user()); // for access user data in any view or layout
}
MessagesController.php
public function index(){
debug($this->user_info);
$my_messages = $this->Message->find('all',
array('conditions' => array('Message.user_id' => $this->user_info['id']))
}
....
layout or view.ctp
<?php echo $user_info_view['name']; ?> // email, etc
Why not take advantage of the way CakePHP handles relationships? There's a very easy way to achieve what you're trying to do without extending controllers or loading in additional controllers which seems excessive for your example.
Inside AppController's beforeFilter()
Configure::write('UserId', $this->Session->read('Auth.User.id'));
This will allow you to access the UserID from your models
Inside your User's model, create the following function
/**
* Sample query which can be expanded upon, adding fields or contains.
*
* #return array The user data if found
*/
public function findByUserId() {
$user = $this->find('first', array(
'conditions' => array(
'User.id' => Configure::read('UserId')
)
));
return $user;
}
Inside your Users controller (Minimal is better, no?)
public function user_info() {
$this->set('user', $this->User->findByUserId());
}
Inside your Messages controller
public function index() {
$this->set('user', $this->Message->User->findByUserId());
// --- Some more stuff here ---
}
And that's it, no need to be extending controllers, just make sure your Message and User model are related to each other, failing that you can bindModel or use ClassRegistry::init('User')-> for example.

Yii - Get data from Model

I have the following in my controller:
public function actionIndex() {
$userID = Yii::app()->user->getId();
$arNotifs = Notification::model()->getNotificationsByUserId($userID);
$this->render('index', array("arNotifications"=>$arNotifs, "userID"=>$userID));
}
I have the following in a file called notification.php in my models:
class Notification extends CActiveRecord {
// ------ BUNCH OF STUFF
public function getNotificationsByUserId($userId) {
$userId = (int) $userId;
$query = Yii::app()->db->createCommand();
$query->select('n.id, n.title, n.content, n.updated');
$query->from('hr4_notification_x_user nxu');
$query->join('hr4_notification n', 'nxu.notification = n.id');
$query->where('nxu.user=:userId', array(':userId' => $userId);
return $query->queryAll();
}
// ------ MORE STUFF
}
When I rem out the line
$arNotifs = Notification::model()->getNotificationsByUserId($userID);
and replace it with a static value it works fine. It seems that in my noob ways I am missing some vital step. The controller seems to have no idea what Notification is.
Thanks in advance
I believe the most elegant way to get your notifications on the controller would be something like:
$arNotifs = Yii::app()->user->model->notifications;
To achieve such, you might need to implement a getModel() method on your class that extends CWebUser. That method would return an instance of an user that extends CActiveRecord. Then your user model can have a relations() method, like the following:
class UserModel extends CActiveRecord {
public function relations() {
return array(
'notifications' => array(self::HAS_MANY, 'Notification', 'user'),
);
}
}
This will prevent you from writing that query and will make things more clear (on both, models and controller). If you will, read a bit about relations.
You cannot use the Notification model like this.
You can instantiate it with $notification = new Notification();
and then do a $notification->getNotificationsByUserId($userID);
However this would be not very good. I would move the notification code from the model to the User model.
This was you dont even need to pass the user ID.
Or maybe even better, if you make a component out of Notification and use it as a service.

codeigniter instance of model class

I'm developing a site with codeigniter. Now, normally when you use a class in codeigniter, you basically use it as if it were a static class. For example, if I head a model called 'user', I would first load it using
$this->load->model('user');
and than, I could invoke methods on that user class like
$this->user->make_sandwitch('cheese');
in the application that I'm building, I would like to have one UserManagement class, which uses a class called 'user'.
so that, for example I could
$this->usermanager->by_id(3);
and this would return an instance of the user model where the id is 3.
What's the best way to do that?
The model classes in CI are not quite the same thing as model classes in other syntax's. In most cases, models will actually be some form of plain object with a database layer which interacts with it. With CI, on the other hand, Model represents the database layer interface which returns generic objects (they're kinda like arrays in some ways). I know, I feel lied to too.
So, if you want to make your Model return something which is not a stdClass, you need to wrap the database call.
So, here's what I would do:
Create a user_model_helper which has your model class:
class User_model {
private $id;
public function __construct( stdClass $val )
{
$this->id = $val->id;
/* ... */
/*
The stdClass provided by CI will have one property per db column.
So, if you have the columns id, first_name, last_name the value the
db will return will have a first_name, last_name, and id properties.
Here is where you would do something with those.
*/
}
}
In usermanager.php:
class Usermanager extends CI_Model {
public function __construct()
{
/* whatever you had before; */
$CI =& get_instance(); // use get_instance, it is less prone to failure
// in this context.
$CI->load->helper("user_model_helper");
}
public function by_id( $id )
{
$q = $this->db->from('users')->where('id', $id)->limit(1)->get();
return new User_model( $q->result() );
}
}
Use abstract factory pattern or even Data access object pattern which does the job that you require.
class User extend CI_Model
{
function by_id($id) {
$this->db->select('*')->from('users')->where('id', $id)->limit(1);
// Your additional code goes here
// ...
return $user_data;
}
}
class Home extend CI_Controller
{
function index()
{
$this->load->model('user');
$data = $this->user->by_id($id);
}
}

Categories