Extbase Mapping with non-TYPO3-table - model-view-controller

I have too classes and two non-TYPO3-tables. I defined a non-TYPO3-table as a table without uid, pid, etc. columns.
My two classes:
class Tx_Abc_Domain_Model_Location extends Tx_Extbase_DomainObject_AbstractEntity
class Tx_Abc_Domain_Model_Facility extends Tx_Extbase_DomainObject_AbstractEntity
My two tables (with columns):
locations
zipcode
city
facility_id
facilities
facility_id
name
I've mapped the attributes like this:
config.tx_extbase.persistence.classes {
Tx_Abc_Domain_Model_Location.mapping {
tableName = locations
columns {
zipcode.mapOnProperty = zipcode
city.mapOnProperty = city
facility_id.mapOnProperty = facility
}
}
Tx_Abc_Domain_Model_Facility.mapping {
tableName = facilities
columns {
facility_id.mapOnProperty = uid
name.mapOnProperty = name
}
}
}
My problem:
The facility attribute of my location model got the type Tx_Abc_Domain_Model_Facility and when I'm looking for a location via the LocationRepository it builds me a location model which contains a facility model.
The problem appears, when I the search I am doing returns several results. i.e. the location with the zipcode 12345 has two different facilities (and the table locations got two rows with different facility_ids), then I would expect to get two location models and each of it got the right facility model.
But instead I get the two location models, which have all same facility model inside. They've got all the facility of the first found location.
Even if I change the type of the facility attribute to integer, there are the wrong ids. But if I enable raw query result in repository I get the correct ids.
I get also the correct ids or models, when I add to both tables an uid-column.
Is there no possibility to map tables without uid column with Extbase models?
Thanks.

Okay, the answer to my last question is: Yes, there is no possibility to map tables without uid column with Extbase models.
There is an existing ticket on forge: http://forge.typo3.org/issues/25984
The reason seems to be the hardcoded $row['uid'] in mapSingleRow() method in Tx_Extbase_Persistence_Mapper_DataMapper class.

If it's not alot of tables you have to map, a work-around could be to create views for those tables to just map the uid.
I.e.:
CREATE VIEW tx_abc_domain_model_facility AS
SELECT facility_id AS uid, facilities.* FROM facilities;

Related

Does eloquent pivot tables work for multi-word table names?

I have two multi-word models, let's call them FunkyModel and AnotherModel.
Will creating a pivot table named another_model_funky_model work?
The docs and examples I've come across all use single word model names like this: model A - User, model B - Address, and pivot table will then be address_user.
If you dive into the source code of the BelongsToMany relation function, you'll find that if you haven't provided a $table, the code will execute the function joiningTable. This uses the current model and the passed related class, snake cases the names and then puts them in alphabetical order of each other.
Simply said, no matter if you have a single word or a couple, the result will always be the 2 classes snaked, in alphabetical order. Note that the alphabetical order is applied by the default php sort.
Examples:
Department + Occupation > department_occupation
AwesomeModel + LessInterestingModel > awesome_model_less_interesting_model
Role + UserPermission > role_user_permission
You can even try and see what the auto-generated name is by simply calling the following:
(new Model)->joiningTable(OtherModel::class, (new OtherModel));
Yes it would work, you can also name it whatever you want, you just need to declare the table name in the relation (same goes for the foreign keys)
class FunkyModel
{
public function anotherModels()
{
return $this->belongsToMany(AnotherModel::class, 'pivot_table_name', 'funky_model_id', 'another_model_id');
}

laravel - how to deal with model of similar type

I am trying to model a company and its relevant employee strucutre. I have 3 tables (company, position, employee) as below, and company haveMany position, and employee haveMany position. Position belongs to company, and position belongs to employee.
However, different position have some common field like onboard date, but have some fields are different. Forexmaple, CEO has a gurantee employment period, while other position dont. Quite a number of field is different too for different position.
In that case, should I using polymorphic to model? but as the company has quite a number of different position, this will create quite a lot new table in the database.
Do you have any advice on how to model different positions?
Companies
id
Position
Positions
id
type [CEO, manager, director, clerk, etc]
company_id
employee_id
Onboard Date
Ceased Date
Employees
id
position id
In that case, should I using polymorphic to model? but as the company has quite a number of different position, this will create quite a lot new table in the database.
No, why would be?
First of all, it should be manyToMany relation and not oneToMany because if you have two companies both of those can have CEO (for example) position and if you set $position->belongsTo(Company::class); it couldn't work.
It is polymorph relation there with positions as polymorphic angle of that triangle.
You would need
// companies
id
name
// employees
id
name
// positions
id
name
// positionables
position_id
positionable_id
positionable_type
With this, your models would be
class Company extends Model
{
public function positions()
{
return $this->morphToMany(Position::class, 'positionable');
}
}
class Employee extends Model
{
public function positions()
{
return $this->morphToMany(Position::class, 'positionable');
}
}
class Position extends Model
{
public function companies()
{
return $this->morphedByMany(Company::class, 'positionable');
}
public function employees()
{
return $this->morphedByMany(Company::class, 'positionable');
}
}
It allows you to set positions, companies and employees separately. Meaning, From dashboard you can make some new positions that will be available on frontend from select options let's say. Of course you should allow company and to employee to create new position (I suggest) and not just to use existing one but it could be out of scope of this question now: in example, when (and if) company creates new position (instead of selecting existing ones from options list), you would first create that position and store it into positions table and then associate company with it. Also, when using this kind of chained inputs to DB don't forget to use DB transactions. Into positionables table you would set other fields important for each relation (onboard_date, ceased_date, etc).
Documentation is very good and consult it if something is not clear (I hope it is already).
Disclaimer: I don't know rest of your project business plan and rest of project's requirements but for these three entities this is the best structure you can go with. I have set just mandatory members to models and tables for this example. Also in offered answer, I presumed use of Laravel's naming convention that's blindly followd from docs and this repo.
If the fields have no relationship with other tables, one possible way is to have a key-value table to store those fields and values:
position_fields
- id
- position_id
- key
- value
You can hence store the fields in key and the respective value in value. Then you may overwrite the __get magic method in Position model e.g.
public function __get($key){
$position_field = $this->hasMany(PositionField::class)->where('key', $field)->first();
return !!$position_field ? $position_field->value : $this->getAttribute($key);
}

How do I query an optional column with a secondary index using phantom?

I have a secondary index on an optional column:
class Sessions extends CassandraTable[ConcreteSessions, Session] {
object matchId extends LongColumn(this) with PartitionKey[Long]
object userId extends OptionalLongColumn(this) with Index[Option[Long]]
...
}
However, the indexedToQueryColumn implicit conversion is not available for optional columns, so this does not compile:
def getByUserId(userId: Long): Future[Seq[Session]] = {
select.where(_.userId eqs userId).fetch()
}
Neither does this:
select.where(_.userId eqs Some(userId)).fetch()
Or changing the type of the index:
object userId extends OptionalLongColumn(this) with Index[Long]
Is there a way to perform such a query using phantom?
I know that I could denormalize, but it would involve some very messy housekeeping and triple our (substantial) data size. The query usually returns only a handful of results, so I'd be willing to use a secondary index in this case.
Short answer: You could not use optional fields in order to query things in phantom.
Long detailed answer:
But, if you really want to work with secondary optional columns, you should declare your entity field as Option but your phantom representation should not be an option in order to query.
object userId extends LongColumn(this) with Index[Long]
In the fromRow(r: Row) you can create your object like this:
Sessions(matchId(r), Some(userId(r)))
Then in the service part you could do the following:
.value(_.userId, t.userId.getOrElse(0))
You also have a better way to do that. You could duplicate the table, making a new kind of query like sessions_by_user_id where in this table your user_id would be the primary key and the match_id the clustering key.
Since user_id is optional, you would end with a table that contains only valid user ids, which is easy and fast to lookup.
Cassandra relies on queries, so use it in your favor.
Take a look up on my github project that helps you get up with multiple queries in the same table.
https://github.com/iamthiago/cassandra-phantom

Retrieving rows based on a certain criteria regarding a many-to-many mapping in Hibernate

I'm just copy & pasting some of the introductory text from one of my questions, since the same table relationship is involved in this question also.
I have three of many tables in Oracle (10g) database as listed below. I'm using Hibernate Tools 3.2.1.GA with Spring version 3.0.2.
Product - parent table
Colour - parent table
ProductColour - join table - references colourId and prodId of Colour and Product tables respectively
Where the table ProductColour is a join table between Product and Colour. As the table names imply, there is a many-to-many relationship between Product and Colour which is mapped by PrductColour. I think, the relationship in the database can easily be imagined and is clear with only this much information. Therefore, I'm not going to explore this relationship at length unnecessarily.
An entity (row) in Product is associated with any number entities in Colour and an entity (row) in Colour can also be associated with any number of entities in Product.
Since, it is a many-to-many relationship, it is mapped in the Product and the Colour entity classes (POJOs) with their respective java.util.Set and no direct POJO class for the product_colour table is available.
The class Product looks like the following.
public class Product implements java.io.Serializable
{
private BigDecimal prodId;
private Set<Colour> colours = new HashSet<Colour>(0);
.
.
.
//Other properties with setters and getters.
}
The class Colour looks like the following.
public class Colour implements java.io.Serializable
{
private BigDecimal colourId;
private Set<Product> products = new HashSet<Product>(0);
.
.
.
//Other properties with setters and getters.
}
The actual mapping between entities is available in xxx.hbm.xml files, regarding this question which is unnecessary, I think .
What I want to do is to retrieve only those rows from the Colour table which don't match the colour rows in the ProductColour table for a particular product at a time. In this regard, the native Oracle SQL statement would look something like the following.
SELECT colour_id, colour_name, colour_hex
FROM colour
WHERE colour_id not in (SELECT colour_id FROM product_colour WHERE prod_id=81)
ORDER BY colour_id DESC
Where prod_id can be any valid BigDecimal number in Java which is dynamic.
As noted earlier, the relationship is available as a many-to-many relationship in Hibernate, no POJO class for the database table product_colour is available and therefore, I'm fumbling in writing such an HQL statement in Hibernate. I have tried to write such an HQL statement but no attempts were succeeded.
[The code presented in the rest of the part may completely be unnecessary to review]
I'm therefore following a traditional way. What I'm doing is... I'm first retrieving a single product row from the Product the entity class based on a dynamic value of prodId such as,
List<Product>list=session.createQuery("from Product where prodId=:prodId")
.setParameter("prodId", prodId).list();
and then using a loop, I'm getting the entire Colour set - java.util.Set corresponding to the product_colour table in Oracle which is available in the Product entity for this product such as,
Set<Colour>colours=new HashSet<Colour>(0);
for(Product p:list)
{
if(p!=null)
{
colours=p.getColours();
}
}
As can be seen, the colours Set is being populated with all of the colour rows available (reference rows) in the product_colour table in Oracle.
After getting all of these rows, I'm getting the entire Colour entity class itself (all the row in it) that corresponds to the colour table in Oracle and then removing those rows which match the rows retrieved from the product_colour Oracle table (available in the colours Set in the preceding snippet) satisfying the condition as mentioned earlier such as,
List<Colour>colourList=session.createQuery("from Colour order by colourId desc").list();
Iterator<Colour>it=colourList.iterator();
while(it.hasNext())
{
Colour c=(Colour)it.next();
for(Colour pc:colours) //colours is available in the preceding snippet.
{
if(c==pc)
{
it.remove();
}
}
}
This can do what is intended but doing so, may imply some overhead on the system. Additionally, what I want to achieve doesn't seem possible with this approach which is pagination. I can't use the setFirstResult(int) and the setMaxResults(int) methods to accomplish the task of pagination which is the case otherwise like the one shown below regarding the Product entity class,
List<Product> products=session.createQuery("from product order by prodId desc")
.setMaxResults(0).setFirstResult(4);
So the question is again, regarding this relationship, is this possible to write such an HQL statement that can retrieve only those rows from the Colour entity class which don't match the colour rows in the product_colour Oracle table like the native SQL statement shown above?
How can I achieve the concept of pagination otherwise (in case, it is not possible)?
Short answer to a veeeeery long question:
select colour from Colour colour
where colour.id not in (
select colour2.id from Product product
inner join product.colours colour2
where product.id = :productId)

Table with a foreign key

how can I build a table of "orders" containing "IdOrder", "Description" and "User"?... the "User" field is a reference to the table "Users", which has "IdUser" and "Name". I'm using repositories.
I have this repository:
Repository<Orders> ordersRepo = new OrderRepo<Orders>(unitOfWork.Session);
to return all Orders to View, I just do:
return View(ordersRepo.All());
But this will result in something like:
IdOrder:1 -- Description: SomeTest -- User: UserProxy123ih12i3123ih12i3uh123
-
When the expected result was:
IdOrder:1 -- Description: SomeTest -- User: Thiago.
PS: I don't know why it returns this "UserProxy123ih12i3123ih12i3uh123". In Db there is a valid value.
The View:
It is showed in a foreach (var item in Model).
#item.Description
#item.User //--> If it is #item.User.Name doesn't work.
What I have to do to put the Name on this list? May I have to do a query using LINQ - NHibernate?
Tks.
What type of ORM are you using? You mention "repositories" but does that mean LinqToSql, Entity Framework, NHibernate, or other?
It looks like you are getting an error because the User field is not loaded as part of the original query. This is likely done to reduce the size of the result set by excluding the related fields from the original query for Orders.
There are a couple of options to work around this:
Set up the repository (or context, depending on the ORM) to include the User property in the result set.
Explicitly load the User property before you access it. Note that this would be an additional round-trip to the database and should not be done in a loop.
In cases where you know that you need the User information it would make sense to ensure that this data in returned from the original query. If you are using LinqToSql take a look at the DataLoadOptions type. You can use this type to specify which relationships you want to retrieve with the query:
var options = new DataLoadOptions();
options.LoadWith<Orders>(o => o.User);
DataContext context = ...;
context.LoadOptions = options;
var query = from o in context.Orders
select o;
There should be similar methods to achive the same thing whatever ORM you are using.
In NHibernate you can do the following:
using (ISession session = SessionFactory.OpenSession())
{
var orders = session.Get<Order>(someId);
NHibernateUtil.Initialize(orders.User);
}
This will result in only two database trips (regardless of the number of orders returned). More information on this can be found here.
In asp.net MVC the foreign key doesn't work the way you are using it. I believe you have to set the user to a variable like this:
User user = #item.User;
Or you have to load the reference sometimes. I don't know why this is but in my experience if I put this line before doing something with a foreign key it works
#item.UserReference.load();
Maybe when you access item.User.Name the session is already closed so NHib cannot load appropriate user from the DB.
You can create some model and initialize it with proper values at the controller. Also you can disable lazy loading for Orders.User in your mapping.
But maybe it is an other problem. What do you have when accessing "#item.User.Name" from your View?

Resources