How to use deal with custom database types in CakePHP ORM? Persist and Fetch Spacial data - orm

Ill try to tell the long story short:
MySQL has a spatial Point data type for some time. To insert data we need to use expression of INSERT (...) VALUES (POINT(lon lat) ...). To do that in CakeORM we need to have our given property as new QueryExpression("POINT($lon,$lat)"); and the CakePHP will handle data binding upon saving entity.
Now what I want to do is to Have entity with property (field) of type GeoJSON Point that corresponds to Mysql column of type Point as well and the ability to store and fetch entities to and from the db
I have used beforeSave call back for that in such manner:
public function beforeSave(Event $event, \Cake\Datasource\EntityInterface $entity, ArrayObject $options) {
$coords = $entity->position->getCoordinates();
$entity->position = new \Cake\Database\Expression\QueryExpression('POINT(' . $coords[0] . ',' . $coords[1] . ')');
}
where $entity->position is of Type Point(additional lib, but this could be anything). This works in general, but modifies actuall entity so if i do
$entity->position=new Point(5,10);
$table->save($position);
$entity->position // <- this will be Query expression insteed of Point(5,10);
And in the third step I want to still have my Point object, and not modified value -QueryExpression.
I know that CakePHP supposed to have support for custom database datatypes but it is useless due to... well it just does not work as I would expect it to work. Here is a GitHub issue describing why remomended (by the docs) solution does not work.
https://github.com/cakephp/cakephp/issues/8041

Related

Sulu: Entity has no field or association error

I'm following Sulu example here: https://github.com/sulu/sulu-workshop/
trying to set translations for custom entity type.
My entity file has getter for field "home_team" defined like:
/**
* #Serializer\VirtualProperty(name="home_team")
*/
public function getHomeTeam(): ?string
{
$translation = $this->getTranslation($this->locale);
if (!$translation) {
return null;
}
return $translation->getHomeTeam();
}
So field is not actually part of that entity, but of it's translation entity since it suppose to be translatable.
When I try to create new object of that entity type it works well. I can see in database that field values are stored well and I don't get any error.
But on overview page instead of list of all objects I get error:
[Semantical Error] line 0, col 73 near 'home_team AS': Error: Class App\Entity\MatchEvent has no field or association named home_team
Any idea what could be wrong here?
If you wanna see the translation in the listView you have to create a real translationEntity, like in the workshop project. In this post it is already explained, how to translate a custom entity correctly.
If you have already created your translationEntity you have to configure the relation of the translation to your main entity via a join. Here is an example in the workshop for this configuration.
Sulu uses optimised queries to create the list-object directly from the database. So the entity itself does not get hydrated or serialised for performance reasons. Thus your virtualProperty is never executed.

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

Phalcon working with MySQL routines

I have a MySQL database which has GUID's stored as binary(16) for the primary keys. I'm using a MySQL user defined routine when inserting and selecting to convert the id's to and from GUID's (GUIDToBinary() and BinaryToGUID()).
In order to use my routines in Phalcon, I am setting the 'columns' parameter for the find() and findFirst() model functions which now means i'm working with incomplete objects as the functions return an instance of Phalcon\Mvc\Model\Row.
The docs state when using the columns parameter the following occurs;
Return specific columns instead of the full columns in the model. When
using this option an incomplete object is returned
UserController.php
// Returns Phalcon\Mvc\Model\Row
$incompleteUser = User::find(['columns' => 'BinaryToGUID(id) as id, status, username, password, .....']);
// Create a new user object to update
$user = new User();
// Populate with existing data
$user->assign($incompleteUser->toArray());
// Assign new changes requested by the user
$user->assign($this->request->getPost());
// Update
$user->updateUser();
User.php
public function updateUser()
{
$manager = $this->getModelsManager();
return $manager->executeQuery("UPDATE User SET ..... WHERE id = GUIDToBinary(".$this->getDI()->get('db')->escapeString($this->id).")");
}
Irrespective of the fact that I've explicitly defined an update, an insert is performed due to the Model being in a transient state.
One solution I thought of was to move the binary to GUID conversion into Phalcon by using Model events however I can't find a suitable method for performing the conversion when selecting. Updating/Inserting is possible by using the beforeSave() and beforeUpdate() events. Perhaps I could just have different properties getId() and getIdGuid() within the model but I would prefer to avoid this if possible.
Is there a way to use MySQL user defined routines in Phalcon and hydrate my model so that it remains in a persistent state? Or do i need to go down the raw SQL route for my updates and avoid PHQL?
Thanks in advance for your time.

Native Query Mapping on the fly openJPA

I am wondering if it is possible to map a named native query on the fly instead of getting back a list of Object[] and then looping through and setting up the object that way. I have a call which I know ill return a massive data set and I want to be able to map it right to my entity. Can I do that or will I have to continue looping through the result set.
Here is what I am doing now...
List<Provider> ObjList = (List<Provider>) emf.createNativeQuery(assembleQuery(organizationIDs, 5)).getResultList();
That is my entity, the List (my entity is the provider). Normally I would just return a List<Object[]>
and then I would loop through that to get back all the objects and set them up as new providers and add them to a list....
//List<Provider> provList = new ArrayList<Provider>();
/*for(Object[] obj: ObjList)
{
provList.add(this.GetProviderFromObj(obj));
}*/
As you can see I commented that section of the code out to try this out. I know you can map named native queries if you put your native query in the entity itself and then call it via createNamedQuery. I would do it that way, but I need to use the IN oracle keyword because I have a list of ID's that I want to check against. It is not just one that is needed. And as we all know, native queruies don't handle the in keyword to well. Any advice?
Sigh, If only the IN keyword was supported well for NamedNativeQueries.
Assuming that Provider is configured as a JPA entity, you should be able to specify the class as the second parameter to your createNativeQuery call. For example:
List<Provider> ObjList = (List<Provider>) emf.createNativeQuery(assembleQuery(organizationIDs, 5), Provider.class).getResultList();
According to the documentation, "At a minimum, your SQL must select the class' primary key columns, discriminator column (if mapped), and version column (also if mapped)."
See the OpenJPA documentation for more details.

JPA & Ebean ORM: Empty collection is not empty

I've started switching over a project from hand-written JDBC ORM code to Ebeans. So far it's been great; Ebeans is light and easy to use.
However, I have run into a crippling issue: when retrieving a one-to-many list which should be empty there is actually one element in it. This element looks to be some kind of proxy object which has all null fields, so it breaks code which loops through the collection.
I've included abbreviated definitions here:
#Entity
class Store {
...
#OneToMany(mappedBy="store",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
List<StoreAlbum> storeAlbums = new LinkedList<StoreAlbum>();
}
#Entity
class StoreAlbum {
...
#ManyToOne(optional=false,fetch=FetchType.EAGER)
#JoinColumn(name="store_id",nullable=false)
Store store;
}
The ... are where all the standard getters and setters are. The retrieval code looks like this:
Store s = server.find(Store.class)
.where()
.eq("store_id",4)
.findUnique();
Assert.assertEquals("Sprint",s.getStoreName());
Assert.assertEquals(0, s.getStoreAlbums().size());
The database is known to contain a 'store' row for "Sprint", and the 'store_album' table does not contain any rows for that store.
The JUnit test fails on the second assertion. It finds a list with 1 element in it, which is some kind of broken StoreAlbum object. The debugger shows the object as being of the type "com.lwm.catalogfeed.domain.StoreAlbum$$EntityBean$test#1a5e68a" with null values for all the fields which are declared as nullable=false (and optional=false).
Am I missing something here?
Thought I'd post an update on this... I ended up giving up on EBeans and instead switched the implementation over to use MyBatis. MyBatis is fantastic; the manual is easy to read and thorough. MyBatis does what you expect it to do. I got it up and running in no time.
EBeans didn't appear to detect that the join for the associated collection resulted in a bunch of null ids, but MyBatis handled this scenario cleanly.
I ran into the same issue and was able to solve it by adding an identity column to the secondary table (StoreAlbum). I did not investigate the cause but I suppose Ebean needs a primary key on the table in these kind of situations.