Kohana ORM store count in many-to-many relationship - kohana-orm

I'm building a application using Kohana 3.2 and Kohana ORM.
The application has systems. Systems contain components. A system may contain multiple components, but also multiple components of the same type. E.g. System_A may have 10 Component_Y and 3 Component_Z
So instead of just having two belongs_to fields in my pivot table I also want to store the count.
If I just use a has-many-through I won't be able to access the count. Without ORM I'd just join the count onto the component in SQL, because the count is unique for the System + Component combination and so I can access the count for the component when I visit the object in the context of a certain system.
How best to go about this in Kohana ORM?

I solved it partially this way:
protected $_has_many = array(
'omvormer' => array(
'model' => 'omvormer',
'through' => 'systeemomvormer'
),
'systeemomvormer' => array(
'model' => 'systeemomvormer',
)
);
I've added the pivot tabel systeemomvormer separately to systeem.
I can now do this:
$so = ORM::factory("systeemomvormer");
$so->where('systeem_id', '=', $systeem_id);
$so->where('omvormer_id', '=', $omvormer_id);
$result = $so->find();
$result->aantal = $omvormer_count;
But it really still only is a partial solution, because I'm not able to update() the result. Kohana says that the result is not loaded. However that's outside the scope of this question and I'll open a new question for that.
This was also helpfull:
http://forum.kohanaframework.org/discussion/7247/kohana-3-orm-save-for-update-a-many-to-many/p1

To store more data in a junction table than just the two keys, you need to create a model for it.
So,
system _has_many system_component
component _has_many system_component
system_component _belongs_to component
system_component _belongs_to system
However, you may not need to store the count if you do it this way. Instead, you can do the following:
$system->components->count_all();
Then, to access them:
foreach($system->components->find_all() as $component)
echo $component->component->name;

Related

One to many CakePHP3 (via matching table)

I was wondering, if it is possible to create a OneToMany Relationship in CakePHP3 via a matching Table in the DB.
This is what my DB looks like:
Showcase of DB Schema
This is my Table for ItemA:
$this->belongsTo('ItemB', [
'foreignKey' => 'item_a_id',
'targetForeignKey' => 'item_b_id',
'joinTable' => 'item_a_item_b'
]);
This is my Table for ItemB:
$this->belongsToMany('ItemA', [
'foreignKey' => 'item_b_id',
'targetForeignKey' => 'item_a_id',
'joinTable' => 'item_a_item_b'
]);
However, when I create a Control for ItemA in the template, it still gives me a Multiple Select.
echo $this->Form->control('item_b._ids', ['options' => $item_b, 'empty' => true]);
When I change this Form to Single Select, the selected Object will not be passed. I'm stuck to a Multiple Select :(
Is this the correct implementation of the DB Schema in Cake? Do I have to use the 'through' option? I'm confused...
Edit#1: It works if I configure ItemA with belongsToMany rather than belongsTo. But that would be a ManyToMany Relationship.
Traditionally, this isn't how you'd structure a one-to-many relationship. To enforce this at the DB level you would simply add tbl_item_a.id to the tbl_item_b table like:
tbl_item_b.item_a_id
However, by the sounds of it you know that already. And personally I don't see anything wrong with the setup as you have it, so long as it's documented (for others in the future).
By the sounds of it. The problem you're having isn't with the DB though, it's with the client-side rendering. For which you will want to use the select FormHelper function:
echo $this->Form->select(/*field name*/, /*[options]*/)

Yii2 activeDropDownList fundamentals

I'm new to Yii2 but not to MVC (trying to move my focus from MS to Yii2). I've scaffolded up an app from my DB using gii. I have a table called track I have a table called music_category. There's a n:n relationship via a link table called track_has_music_category. My track model (as scaffolded by gii) has a function
public function getMusicCategories()
{
return $this->hasMany(MusicCategory::className(), ['id' => 'music_category_id'])->viaTable('track_has_music_category', ['track_id' => 'id']);
}
But that is for retrieving the categories that have been 'link'ed to this track. So I guess I need something like
<?= Html::activeDropDownList(theCategoryModel (syntax here??), 'id', ArrayHelper::map(theCategoryModel->findAll(),'id', ''description)) ?>
Well, something like that. I feel as though the online reference could do with a few worked examples.
Any help much appreciated.
Cheers
Mark
Let's say we want to attach some categories to a track model ($model):
echo Html::activeCheckboxList($model, 'musicCategories', ArrayHelper::map(MusicCategory::findAll(),'id', ''description))
We use activeCheckboxList here since it's a multiple relation.
Although you will need to add extra $model logic to save these relations.
Have a look at this How do I work with many-to-many relations in Yii2

Design best practice - model vs controller vs UI - CakePHP, MySQL

I have been struggling for a few days with this problem and finally seek the opinion of the experts and crowd at this website.
I have two tables - one is a template of workflow steps and the other is an instance of these workflow steps called events. The templates table contains information like step name, step type etc - very generic information. The event table contains a reference link back to the workflow step table and an additional column called notes - which stores data that the user logged as they logged a particular workflow step. Both Workflow Steps and Events are linked to a POST on the website
Workflow step templates can exist without events having yet occurred - that is the user may be still on Step 3 or Step 5 and not logged an event for Step 1, 2 , 4 - basically the order of steps is only suggestive but not binding. Workflow Steps have a sequence field that dictate the order in which they should appear on screen.
Events can also occur without a workflow step - in other words, a user can log a note outside the context of workflow steps. These are generic events and directly associated with the POST
I am able to successfully retrieve both of these values for a given POST - they are retrieved as two separate arrays. I am using CakePHP and MySQL
The UI needs to render a screen that shows all the workflow steps in order and corresponding events that have occured in correlation to these steps or outside of these steps. The ordering of the screen will be driven primarily by the sequence of workflow steps and secondarily by created_date for those events that are not associated with a particular workflow step
Problem statement -
1. Do I send two separate arrays (as noted in #4) to the UI and let the UI determine the complex logic of how to interweave the steps and events for display?
2. Do I process the interweaving of steps and events in the controller and then send to the UI a simple array that it can loop through and display?
3. I have tried moving this logic to the database but because of variations explained in #2 and #3 it becomes quite complicated
I am seeking advise on which would be a better option from a design practice as well as from a simplification point of view. I understand that I have given a limited picture here but am hoping that someone on this website may have run into a similar issue elsewhere.
Depending on how you are assigning events to users, I would make a hasOne relation in Event to Workflow. You would need another relationship for you users, hasOne or hasMany.
$hasOne = 'Workflow';
Obviously this would mean that your Event table would have a column called wordflow_id and would be associated with a single row in your workflow. In the controller I would call the Event with by the user.
$this->Event->findAllByUserId($user_id);
This should provide you with an array that might look something like this.
array(
[0] => array(
[Event] => array(
'id' => 1,
'name' => 'blah',
...
),
[User] => array(
'id' => 1,
'name' => 'Charles',
...
),
[Workflow] => array(
[0] => array(
'id' => 1,
'name' => 'more blagblagblag',
...
),
[1] => array(
'id' => 9,
'name' => 'sblagblsagblag',
...
),
[2] => array(
'id' => 42,
'name' => 'mordse d',
...
)
)
)
)
Call all your workflow templates
$this->Workflow->find('all');
Then I would user cake's built in SET:: functionality to print the workflow template in your view and use your Event call to fill in the data.
Please post more detail and your code, models, ect and I'm sure we can get you the exact query/logic you'll need to achieve this.
http://book.cakephp.org/2.0/en/core-utility-libraries/set.html
OK - I have solved this. I ended up moving the functionality to the Model.
I created two SQL queries - one that retrieves all the workflow steps along with any event information that maybe associated with each of them.
Then I created a second SQL that retrieves all those events that are stand-alone and not associated with any particular workflow step
I used UNION ALL to stack them on top of each other
I used a SORT on modified date and squence number so that all the steps and events appear in chronological order and sequence
I then passed this from the Model to the View (via the controller) and let the View iterate and display the elements. This approach actually simplified my View and Controller code immensely and even the Model code is quite simple since all it is a query statement with parameters.

ATK4 How OneToMany retion can be used with multiselect?

I have 2 MySQL DB tables called roles (representing user access role) and modules (representing allowed modules per role). Each role can have many modules and each module can have many roles.
I want to add a crud representing roles table with a multiselect field allowing to select all related modules. What is the best way to do that. Thanks in advance.
There can be multiple solutions for this.
First one - use User CRUD with expander with Roles (should work):
$crud = $this->add('CRUD');
$crud->setModel('User');
if (! $crud->isEditing()) {
// add subCRUD
$sub_crud = $crud->addRef('UserRole', array(
'extra_fields' => array('role'),
'view_options' => array('entity_name' => 'Role'),
'label' => 'Roles'
));
}
Second one - use Grid with Roles + grid->addSelectable($fields) (not tested, but just to give you idea):
$grid = $this->add('Grid');
$grid->setModel('UserRole');
$grid->addSelectable('selected');
Third one - use two lists with roles (available roles and associated roles) and some buttons to "move" role from one list to another.
Something like this: (can't find link to appropriate Codepad page now) :(
There definitely can be even more ways to do this.

Exposing table name and field names in request URL

I was tasked to create this Joomla component (yep, joomla; but its unrelated) and a professor told me that I should make my code as dynamic as possible (a code that needs less maintenance) and avoid hard coding. The approach we thought initially is take url parameters, turn them into objects, and pass them to query.
Let's say we want to read hotel with id # 1 in the table "hotels". lets say the table has the fields "hotel_id", "hotel_name" and some other fields.
Now, the approach we took in making the sql query string is to parse the url request that looked like this:
index.php?task=view&table=hotels&hotel_id=1&param1=something&param2=somethingelse
and turned it into a PHP object like this (shown in JSON equivalent, easier to understand):
obj = {
'table':'hotel',
'conditions':{
'hotel_id':'1',
'param1':'something',
'param2':'somethingelse'
}
and the SQL query will be something like this where conditions are looped and appended into the string where field and value of the WHERE clause are the key and value of the object (still in JSON form for ease):
SELECT * FROM obj.table WHERE hotel_id=1 AND param1=something and so on...
The problem that bugged me was the exposing of the table name and field names in the request url. I know it poses a security risk exposing items that should only be seen to the server side. The current solution I'm thinking is giving aliases to each and every table and field for the client side - but that would be hard coding, which is against his policy. and besides, if I did that, and had a thousand tables to alias, it would not be practical.
What is the proper method to do this without:
hard coding stuff
keep the code as dynamic and adaptable
EDIT:
Regarding the arbitrary queries (I forgot to include this), what currently stops them in the back end is a function, that takes a reference from a hard-coded object (more like a config file shown here), and parses the url by picking out parameters or matching them.
The config looks like:
// 'hotels' here is the table name. instead of parsing the url for a table name
// php will just find the table from this config. if no match, return error.
// reduces risk of arbitrary tables.
'hotels' => array(
// fields and their types, used to identify what filter to use
'structure' => array(
'hotel_id'=>'int',
'name'=>'string',
'description'=>'string',
'featured'=>'boolean',
'published'=>'boolean'
),
//these are the list of 'tasks' and accepted parameters, based on the ones above
//these are the actual parameter names which i said were the same as field names
//the ones in 'values' are usually values for inserting and updating
//the ones in 'conditions' are the ones used in the WHERE part of the query
'operations' =>array(
'add' => array(
'values' => array('name','description','featured'),
'conditions' => array()
),
'view' => array(
'values' => array(),
'conditions' => array('hotel_id')
),
'edit' => array(
'values' => array('name','description','featured'),
'conditions' => array('hotel_id')
),
'remove' => array(
'values' => array(),
'conditions' => array('hotel_id')
)
)
)
and so, from that config list:
if a parameters sent for a task is not complete, server returns an error.
if a parameter from the url is doubled, only the first parameter read is taken.
any other parameters not in the config are discarded
if that task is not allowed, it wont be listed for that table
if a task is not there, server returns an error
if a table is not there, server returns an error
I actually patterned this after seeing a component in joomla that uses this strategy. It reduces the model and controller to 4 dynamic functions which would be CRUD, leaving only the config file to be the only file editable later on (this was what I meant about dynamic code, I only add tables and tasks if further tables are needed) but I fear it may impose a security risk which I may have not known yet.
Any ideas for an alternative?
I have no problem with using the same (or very similar) names in the URL and the database — sure, you might be "exposing" implementation details, but if you're choosing radically different names in the URL and the DB, you're probably choosing bad names. I'm also a fan of consistent naming — communication with coders/testers/customers becomes much more difficult if everyone calls everything something slightly different.
What bugs me is that you're letting the user run arbitrary queries on your database. http://.../index.php?table=users&user_id=1, say? Or http://.../index.php?table=users&password=password (not that you should be storing passwords in plaintext)? Or http://.../index.php?table=users&age=11?
If the user connected to the DB has the same permissions as the user sitting in front of the web browser, it might make sense. Generally, that's not going to be the case, so you'll need some layer that knows what the user is and isn't allowed to see, and that layer is a lot easier to write correctly by whitelisting.
(If you've stuck enough logic into stored procedures, then it might work, but then your stored procedures will hard-code column names...)
When composing a SQL query with data from the input, it presents a security risk. But keep in mind that columns values are inserted to the fields by taking input from the user, analyzing it and composing a SQL query with it (except for prepared statements). So when done properly, you have nothing to worry about - simply restrict the user to those column & tables. Open source software's code/database is visible to all - and it doesn't harm the system so much as one would think.
Your aliasses could be a rot13() on the meta/name of your objects.
Although, if you escape the input accordingly when working with those names, I don't see any problem in exposing their names.