Converting Propel to JSON with linked entities - php

I have this setup in a PHP project using Propel (which I'm new to).
User: userid, firstname, lastname
SignIn: signinid, userid, time
SignIn, in this case, is a table containing the times each user signed in.
What I want is to print out a JSON string using Propel of the last ten SignIn entries. On the front end I want to display something like:
Bob Builder signed in at 3:30pm
Max Power signed in at 3:24pm
...
So when I query the last ten entries in SignIn and then call toJSON, I'd like to have the User data for that SignIn record also included in the JSON.
How can I do that?
Note, if there's a better way to do this, I'm open to ideas.

One thing about Propel, is that the documentation is really clean, very readable and always helpful.
For your request, you can try the following request. Take a closer look at it, it's readable (as always with Propel)
$signIn = SignInQuery::create()
->select(array('User.firstname', 'User.lastname', 'SignIn.time'))
->join('User')
->orderByTime('desc')
->limit(10)
->find()
->toJson();

Just an alternate solution: you can also override any of the *Base methods in your classes, I've done this many times to add extra info to the toJSON, toArray, or fromArray methods.
In SignIn.php:
public function toJSON() {
$fields = json_decode(parent::toJSON());
$user = $this->getUser(); // assumes you have a foreign key set up
$fields->firstname = $user->getFirstname();
$fields->lastname = $user->getLastname();
return json_encode($fields);
}
Now you just query for your SignIn objects and whenever toJSON is called the user data will be appended:
$signIn = SignInQuery::create()
->orderByTime('desc')
->limit(10)
->find()
->toJson();
Note that the toJSON override is actually an override of the generic "to[Format]" method in BaseObject which is handled by the code on line 380 (in v1.6): public function __call($name, $params) { ... }

Related

Why query result suddenly changed after called from other medthod in Laravel

I have problem here with query result from Eloquent, I tried to query from DB and put in variable $contractList in my mount() method and the result as expected. But when I tried to retrieve specific data from $contractList with $contractList->find($id), the result not same as in mount() method.
Here is query from mount():
public function mount(){
$contractList = Kontrak::select(['id', 'mou_id'])->with(['order:id,kontrak_id', 'order.worklist:id', 'order.worklist.khs:id,mou_id,worklist_id,khs', 'amdNilai:id,kontrak_id,tgl_surat'])->withCount('amdNilai')->get()
}
Here the result:
But when I tried to find specific data from $contractList, properties that shown not same as in mount:
public function itemSelected($id)
{
//amd_nilai_count not showing
$kontrak = $this->contractList->find($id);
if ($kontrak->amd_nilai_count == 1) {
$this->nilai_amd = $this->calculateNilai($id);
}
}
Here is the result called from itemSelected():
I have tried use get() but the result still problem, how to get same properties same as in mount().By the way im use laravel & livewire.
As i read your comments you seem to mix up ActiveRecords ORM with an Data Mapper ORM. Laravel uses active records.
Laravel will always fetch models on every data operation.
Kontrak::select('name')->find(1); // select name from kontraks where id = 1;
Kontrak::find(1); // select * from kontraks where id = 1;
This will always execute two SQL calls no matter what and the objects on the heap will not be the same. If you worked with Doctrine or similar, this would be different.
To combat this you would often put your logic in services or similar.
class KontrakService
{
public function find(int $id) {
return Kontrak::select(['id', 'mou_id'])->find($id);
}
}
Whenever you want the same logic, use that service.
resolve(KontrakService::class)->find(1);
However, many relationship operations is hard to do with this and then it is fine to just fetch the model with all the attributes.

Getting the rows created before the selected one

I was wondering about the best way to get the count of all the rows created before the selected one. Right now I have defined an accessor that looks like this:
// In the model
public function getPositionAttribute() {
return self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
// In the code
$model->position
It works correctly, but I'm worried about 2 things:
Is it a bad practice to call self on the model? Looks somehow off to me.
When called in a foreach this obviously generates a query for each element which is far from optimal. Is there any way to refactor this so that it can be eager loaded in a single query?
Bonus: I have totally discarded the idea of keeping a column with some kind of index because that initially sounded impossible to maintain, eg. when a record is deleted all the others should somehow shift position. Should I reconsider it? Is there a better way?
Pretty sure that using self here is the "best practice" because that is how that keyword was designed to be used.
In regards to refactoring, i personally can't think of optimizing the query as is but instead you could create a function that preloads all the position then use it normally. Assuming your model has a unique key 'id' and you are passing in a collection of model then, you can try something like this:
public static function populateOrderPositions($modelCollection){
// Optimize this query to include your "other condition"
$idCollection = Model::orderBy('created_at') // this will make it in the order of creation
->pluck('id'); // this will only retrieve the id field
// This array will contain an array with the model object ids as key and a numeric position (starts at 0)
$positionArr = $idCollection->flip()->all();
// Then just load all the position into the object and return it.
return $modelCollection->map(function($modelObj) use ($positionArr){
return $modelObj->position = $positionArr[$modelObj->id] + 1; // +1 because array key starts at 0
};
}
You would also need to adjust your attribute code to use the loaded attribute instead of ignoring the loaded attribute like so:
public function getPositionAttribute() {
return $this->attributes['position'] ?? self::where([
// Some other condition
['created_at', '<', $this->created_at->toDateTimeString()]
])->count();
}
With these changes, you can then prepopulate the position then use it afterward without the need to query the database.
These code are untested as i don't know how your model and query will be structured and is more of an example. Also you would need to compare the performance vs your original code.

PHP - Would i need to create 2 separate objects to hold database data from 2 separate tables?

I may no be asking this questions right but here goes... I have a database with 2 tables "users" (for users name/password/etc) and "usersInfo" (users first name/last/address/etc). I only have 1 Users.php class- do i need 2 separate classes to create 2 objects from to hold the "users" & "usersInfo" data for the same user or will 1 class work (and still make 2 objects?)?
some of my Users.php class/
public function __construct($user = null) {
$this->_db = DB::getInstance();
$this->_sessionName = Config::get('session/session_name');
$this->_cookieName = Config::get('remember/cookie_name');
if(!$user) {
if(Session::exists($this->_sessionName)) {
$user = Session::get($this->_sessionName);
if($this->find($user) || $this->findUserInfo($user)) {
$this->_isLoggedIn = true;
} else {
//logout
}
}
} else {
$this->find($user);
$this->findUserInfo($user);
}
}
public function find($user = null) {
if($user) {
$field = (is_numeric($user)) ? 'id' : 'username';
$data = $this->_db->get('users', array($field, '=', $user));
if($data->count()) {
$this->_data = $data->first();
return true;
}
}
return false;
}
public function findUserInfo($user = null) {
if($user) {
$test3 = $this->_db->get('users', array('username', '=', $user));
$userId = $test3->first()->id;
$data2 = $this->_db->get('usersInfo', array('user_id', '=', $userId));
if($data2->count()) {
$this->_userInfoData = $data2->first();
return true;
}
}
return false;
}
public function data() {
return $this->_data;
}
public function userInfoData() {
return $this->_userInfoData;
}
Currently I have to create 2 objects to use all the data i need for the same user.
for example, in on of my pages.php i have:
$user = new User();
$user1 = new User($user->data()->username);
$userNane = $user->data()->username; //holds users username form "users" table
$userName1 = $user1->userInfoData()->first_name; // holds users first name from "usersInfo" table
It works but doesnt look right... is it efficient/ok practice/etc. If not, suggestions?
Also, first post, take it easy :)
What is the relationship between the 2 tables?
1-1 ? 1-*? Can a user have multiple persona? Can a persona correspond to many user accounts? Will this relation change in the future?
Depending on your answers, you might see which solution fit better your plans.
1-1 relation: you can afford to have a single class to hold related records. It will be easier to manage from the perspective of your application
Otherwise, you'll need at one time or another to handle a record separately from related records in the other table. You'll be better off with 2 distincts objects.
if you plan to change things later on for the second situation, you should keep things as they are.
In the specific case of user data, your comment bring the insight that certain data are more sensitive than others. In retrospect, I guess that's the reason you made these two tables separate. From that point of view, it is certainly better to keep both objects separate, even in a 1-1 relationship.
Regarding your code, indeed, having a dedicated UserInfo class, rather than piggy backing on another instance of User, would clearly be a good thing. A very important idea of good design is separation of concerns: you want each class to handle one and only one purpose, so that any modification to a class will have a limited scope of impact on the rest of the code.
As an example, the only thing you need to retreive a userinfo row, and therefore construct an object wrapping it, is the user id. Instead of delegating the whole job to a method of User, I would probably extract the iout of the User instance, and pass it to the adhoc UserInfo constructor or static method: there, each class only deals with things in its own perimeter. Of course, findUserInfo could also delegate to that same function.
IMO, on of the most important steps in designing/developing an app is creating a sound schema and model. Not sure how much database design experience you have, but you will want to read up on First Normal Form (1NF), and eventually (2NF and 3NF).
Part of the schema design stage is to identify all the nouns which you will reference in your app, in your case a user is a perfect example. Each of these identified nouns will then have attributes, which you will want to consider, in how each will be stored.
The problem in your situation is that you have user and user_info. As you stated user is for name, password, etc, whereas user_info is for first_name, last_name, address etc. Part of the design stage is to determine which of these attributes are directly attributable to the user object, and which are more ancillary in nature. Using your example: name, password, first_name, last_name are each directly attributable to the user noun (object), however address is more ancillary in nature, and there may be more than one address per user (billing address, vs physical address), so you may want to consider adding a user_address table. As you can see, by logically separating the attributes of the user noun (object), you start to identify relationships which make more sense (user, user_address) vs (user, user_info).
Once you identify the nouns, and separate their attributes, you can create your schema. From your schema you can use an Object Relational Mapper (ORM) like Doctrine, which will introspect your schema, and generate objects for you to use throughout your app. In your example you would end up with two objects; User and UserAddress. Also it's important that when developing your schema that you identify relationships between tables by implementing a foreign key constraints. For example, your user_address table should have a user_id column, which links to your user table. This way when doctrine introspects your schema, it will also identify these relationships, which makes coding much easier.
Once you have your ORM in place, you can then make code references like this:
// In your controller
$this->user = UserTable::findById($_SESSION['user_id']);
// Then in your view
Welcome <?php echo $user->getFirstName() ?>, to our wonderful app.
We have your addresses listed as follows:
<?php foreach ($user->getUserAddress() as $userAddress) ?>
<div>
<?php echo $address->getStreet() ?>
</div>
<?php endforeach ?>
Yes, it's a very simplistic example, but should properly demonstrate that if you design your schema properly, the code becomes semantic, which makes it easier to write, and maintain.

Best way to filter access to controller actions according to a specific client id

Using CakePHP 2.2, I am building an application in which each client has it's own "realm" of data and none of the other data is visible to them. For example, a client has his set of users, courses, contractors and jobs. Groups are shared among clients, but they cannot perform actions on groups. All clients can do with groups is assign them to users. So, an administrator (using ACL) can only manage data from the same client id.
All my objects (except groups, of course) have the client_id key.
Now, I know one way to get this done and actually having it working well, but it seems a bit dirty and I'm wondering if there is a better way. Being early in the project and new to CakePHP, I'm eager to get it right.
This is how I'm doing it now :
1- A user logs in. His client_id is written to session according to the data from the user's table.
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
2- In AppController, I have a protected function that compares that session id to a given parameter.
protected function clientCheck($client_id) {
if ($this->Session->read('User.client_id') == $client_id) {
return true;
} else {
$this->Session->setFlash(__('Invalid object or view.'));
$this->redirect(array('controller' => 'user', 'action' => 'home'));
}
}
3- Im my different index actions (each index, each relevant controller), I check the client_id using a paginate condition.
public function index() {
$this->User->recursive = 0;
$this->paginate = array(
'conditions' => array('User.client_id' => $this->Session->read('User.client_id'))
);
$this->set('users', $this->paginate());
}
4- In other actions, I check the client_id before checking the HTTP request type this way.
$user = $this->User->read(null, $id);
$this->clientCheck($user['User']['client_id']);
$this->set('user', $user);
The concept is good - it's not 'dirty', and it's pretty much exactly the same as how I've handled situations like that.
You've just got a couple of lines of redundant code. First:
$this->Auth->user('id')
That method can actually get any field for the logged in user, so you can do:
$this->Auth->user('client_id')
So your two lines:
$user = $this->User->read(null, $this->Auth->user('id'));
$this->Session->write('User.client_id', $user['User']['client_id']);
Aren't needed. You don't need to re-read the User, or write anything to the session - just grab the client_id directly from Auth any time you need it.
In fact, if you read http://book.cakephp.org/2.0/en/core-libraries/components/authentication.html#accessing-the-logged-in-user it even says you can get it from outside the context of a controller, using the static method like:
AuthComponent::user('client_id')
Though it doesn't seem you'll be needing that.
You could also apply the client_id condition to all finds for a Model by placing something in the beforeFind function in the Model.
For example, in your User model, you could do something like this:
function beforeFind( $queryData ) {
// Automatically filter all finds by client_id of logged in user
$queryData['conditions'][$this->alias . '.client_id'] = AuthComponent::user('client_id');
return $queryData;
}
Not sure if AuthComponent::user('client_id') works in the Model, but you get the idea. This will automatically apply this condition to every find in the model.
You could also use the beforeSave in the model to automatically set that client_id for you in new records.
My answer may be database engine specific as I use PostgreSQL. In my project I used different schema for every client in mysql terms that would be separate database for every client.
In public schema (common database) I store all data that needs to be shared between all clients (objects that do not have client_id in your case), for example, variable constants, profile settings and so on.
In company specific models I define
public $useDbConfig = 'company_data';
In Controller/AppController.php beforeFilter() method I have this code to set schema according to the logged in user.
if ($this->Session->check('User.Company.id')) {
App::uses('ConnectionManager', 'Model');
$dataSource = ConnectionManager::getDataSource('company_data');
$dataSource->config['schema'] =
'company_'.$this->Session->read('User.Company.id');
}
As you see I update dataSource on the fly according to used company. This does exclude any involvement of company_id in any query as only company relevant data is stored in that schema (database). Also this adds ability to scale the project.
Downside of this approach is that it creates pain in the ass to synchronize all database structures on structure change, but it can be done using exporting data, dropping all databases, recreating them with new layout and importing data back again. Just need to be sure to export data with full inserts including column names.

Custom object returned by join in Codeigniter Active record

I have these two database tables:
locations
id
name
users
id
location_id
last_name
first_name
I also have User and Location class, they both extends Model and contain some custom methods. For example, there is a get_full_name() method in the User class.
I am using the following codes to load the data,
$this->db->select('users.id, locations.name as location_name, users.last_name, users.first_name');
$this->db->from('users');
$this->db->join('locations', 'users.location_id = location.id');
$query = $this->db->get();
$users= $query->custom_result_object('User'); //now $users is an array of User object
The custom_result_object is a built-in but undocumented function in Codeigniter. It accepts a string of class name and will use it to create objects of that class.
With the above codes, I can access the data like this,
foreach($users as $user)
{
echo $user->id;
echo $user->get_full_name();
echo $user->location_name;
}
But as you can see, location_name is now a field of the User object, what I want is the User object has a field of Location object that allows me to use it like this,
foreach($users as $user)
{
echo $user->id;
echo $user->get_full_name();
echo $user->location->name; // Location is an object
}
I don't want to use DataMapper ORM or any other ORMs, and would also like to avoid the N+1 query performance issue.
How can I do this with minimal amount of codes?
Note: I made up this example just for the demonstration purpose, in my real case, there are quite a lot of tables and classes (which has custom methods defined on them).
Many thanks to you all.
Have you considered the following?
class User {
var location;
public function location()
{
if ( empty($this->location) )
{
$ci =& get_instance();
$this->location = $ci->db->get_where('locations', array('id' => $this->location_id))->row();
}
return $this->location;
}
}
This way, you completely avoid the overhead of loading the location's table unless you need the data, in which case you cache it inside the object for future use.
I had questioned the same for myself but found the simplest way was to iterate through each result (in your case, user) and select the corresponding child (location) and set to the field in the parent object (user->location).
The only alternative way I can think of would involve editing (or extending and creating your own db set) the db_result class to use getters/setters instead of direct fields - perhaps with call_user_func_array(). At that point, you could code your setId() function in the user model to set the $location field upon receiving the userId/foreign key.
get location id from table user, use it's number to grab from table locations by id a object. and put in it.

Categories