TYPO3 Extension not fitting the Extbase Schema - typo3

Since the implementation of Doctrines DBAL we now can use different data-sources/connections (Hooray!).
We'd now like to embed one of our services within TYPO3 using this new feature.
The problem now is, that the domain models in this secondary database don't match the typical Extbase schema. E.g. it does't use int IDs nor are these models nested by any kind of parent ID.
Does anyone have experiences with this use-case or has an example?
Or do I have to setup an complete parallel Doctrine ORM to handle those models?

You could try to map your database fields, like here: https://docs.typo3.org/typo3cms/ExtbaseFluidBook/6-Persistence/4-use-foreign-data-sources.html

I am not sure I understood your question correctly but we had a similar case. What we did is we added the second database in the connections.
LocalConfiguration.php
'DB' => [
'Connections' => [
'Default' => [
'charset' => 'utf8',
'dbname' => 'default_dbname',
'driver' => 'mysqli',
'host' => 'default_dbhost',
'password' => 'default_dbpwasswd',
'user' => 'default_dbuser',
],
'Secondary' => [
'charset' => 'utf8',
'dbname' => 'secondary_dbname',
'driver' => 'mysqli',
'host' => 'secondary_dbhost',
'password' => 'secondary_dbpasswd',
'user' => 'secondary_dbuser',
],
],
],
Then added a custom class that gets the data from the Database with the Query Builder
You first have to open a connection to your database, then create a query Builder Object for that connection in the class you use to get the data.
/**
* #var Connection
*/
private $connection;
$connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
$this->secondaryConnection = $connectionPool->getConnectionByName('Secondary');
$queryBuilder = $this->secondaryConnection->createQueryBuilder();
// now you can get the data with query statements as linked above
$statement = $queryBuilder->select('your_field')->from('your_table')->execute();
while ($row = $statement->fetch()) {
$myObjects[] = new \Vendor\Extension\Domain\Model\ExternalObject($row);
}
I cast each result row to a "ExternalObject" Model that I added, it mapped the fields as closely as possible to my conventional Extbase Objects and then was able to use them almost interchangeably.
It depends on the Data you have in your database though, and the things you want to do with them.

Related

TYPO3 Extbase TCA MM-relation filter results by site-defined constant "newRecordStoragePid"

TLDR:
I've got two models with MM-Relations with different StoragePids defined via Constants in my template.
I don't know how to filter results while querying my data regarding my configured storagePids for my related Model.
Long version:
In my multisite-TYPO3 installation I've got two models "Person" and "PersonalInformation". These models have a MM-Relation defined via TCA.
"Person" contains all general data, stored in a global RecordStore. "PersonalInformation" contains editable Data i.e. images to be editable for each site separately. These data are stored in seperate RecordStores under each site.
That means within each site-template->Constants I've defined the extension-storagePid i.e.: $plugin.tx_myext.persistence.storagePid = 1
This config is on all sites the same, to be able to access the same RecordStore from each Site.
The RecordStore for "PersonalInformation" should be different for each site. So my setup.txt of my extension looks like:
persistence {
storagePid = {$plugin.tx_myext.persistence.storagePid},
{$plugin.tx_tx_myext.persistence.personalInformationStoragePid}
classes {
TYPO3\T3myext\Domain\Model\PersonalInformation {
newRecordStoragePid = {$plugin.tx_myext.persistence.personalInformationStoragePid}
}
}
}
And in my root-site-template under Constants I've defined plugin.tx_myext.persistence.personalInformationStoragePid for each site individually.
My TCA MM-Relation defined for PersonalInformation:
'person' => array(
'exclude' => 1,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_person',
'config' => array(
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_myext_domain_model_person',
'foreign_table_where' => 'AND 1=1 ORDER BY last_name ASC',
'MM' => 'tx_myext_person_personalinformation_mm',
'size' => 10,
'autoSizeMax' => 30,
'maxitems' => 1,
'minitems' => 0,
'multiple' => 0,
),
),
My TCA MM-Relation defined for Person:
'personalinformation' => array(
'exclude' => 1,
'label' => 'LLL:EXT:myext/Resources/Private/Language/locallang_db.xlf:tx_myext_domain_model_person.personalinformation',
'config' => array(
'type' => 'none',
'readonly' => 1,
'foreign_table' => 'tx_myext_domain_model_personalinformation',
'MM_opposite_field' => 'personalinformation',
'MM' => 'tx_myext_person_personalinformation_mm',
'foreign_table_where' => 'AND tx_myext_domain_model_personalinformation.pid=###The-PID-defined-in-my-site-Const-for-personalInformationStoragePid###'
),
),
If I var_dump my Person in the Frontend all Person.PersonaInformation of all RecordStores are displayed. But I what to show only PersonalInformation Records of the current Site.
The field in the model will always give back all relations, independent of the storage pid. The foreign_table_where in TCA is only for the backend, so this will do nothing for the frontend.
If you want to only get relations from a certain pid, there are several solutions:
Filter it yourself, either in your template, model or controller. Just loop through the relations and check the pid. This option is easiest, but will be slow if you have a lot of relations.
Select the PersonalInformation records separately in your controller using a PersonalInformationRepository with a findByPerson function. This will respect the storagePid set in TypoScript. This will work fine if you only need the information for 1 person. If you need it for multiple persons on 1 page (in a list view for example) you can do this in a custom getPersonalInformation function in your Person model. If it's not cached it could also be slow for lists (depending on the amount of records).
Use a completely custom query using QueryBuilder (https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html). This way you can do it in 1 query with joins.
What is the best solution depends on your exact situation and the number of records.

How to set table schema at table name with Cake?

PostgreSQL, Oracle and many other DBMS's use SCHEMA, so, the table name is
schema_name.table_name
But CakePHP manuals not say anithing about this. What about Model, View and Controller names in the CakePHP defaults? I can use a solution like prefix, that is, where the same schema name will be used at all database operations.
PS1: please not to be confused with method Modelschema, and questions about accessing this method.
PS2: the Bill's 2006 solution is not the better one, because is not updated (I am using CakePHP2) and is not a "official cakePHP solution".
PS3: database.php have some schema attribute? What the link to CakePHP documentation?
Good news for me, there are CakePHP 2.0 documentation about SQL-Schema... No other documentation or examples, but a starting point...
In CakePHP you must define more database config.
In CakePHP 2:
set the 'schema' param to your config
create new configs for all of your schema
use the right schema in your models
For example, database conf:
public $default = array(
'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => 'localhost',
'login' => 'my_db_user',
'password' => 'my_db_passw',
'database' => 'my_project_db',
'prefix' => '',
'encoding' => 'utf8',
'schema' => 'postgres'
);
public $other_schema = array(
'datasource' => 'Database/Postgres',
'persistent' => false,
'host' => 'localhost',
'login' => 'my_db_user',
'password' => 'my_db_passw',
'database' => 'my_project_db',
'prefix' => '',
'encoding' => 'utf8',
'schema' => 'other_schema'
);
If you want to use it in a model:
class AppModel extends AppModel {
public $useDbConfig = 'other_schema';
}
In CakePHP3 is the same way, just there the database is config/app.php and you must use
use Cake\Datasource\ConnectionManager;
$connection = ConnectionManager::get('default');

Zend_Validate_Db_RecordExists against 2 fields

I usualy use Zend_Validate_Db_RecordExists to update or insert a record. This works fine with one field to check against. How to do it if you have two fields to check?
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector,day_of_week'
)
);
if ($validator->isValid($fields_values['id_sector'],$fields_values['day_of_week'])){
//true
}
I tried it with an array and comma separated list, nothing works... Any help is welcome.
Regards
Andrea
To do this you would have to extend the Zend_Validate_Db_RecordExists class.
It doesn't currently know how to check for the existence of more than one field.
You could just use two different validator instances to check the two fields separately. This is the only work around that I can see right now besides extending it.
If you choose to extend it then you'll have to find some way of passing in all the fields to the constructor ( array seems like a good choice ), and then you'll have to dig into the method that creates the sql query. In this method you'll have to loop over the array of fields that were passed in to the constructor.
You should look into using the exclude parameter. Something like this should do what you want:
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => $this->_name,
'field' => 'id_sector',
'exclude' => array(
'field' => 'day_of_week',
'value' => $fields_values['day_of_week']
)
);
The exclude field will effectively add to the automatically generated WHERE part to create something equivalent to this:
WHERE `id_sector` = $fields_values['id_sector'] AND `day_of_week` = $fields_values['day_of_week']
Its kind of a hack in that we're using it for the opposite of what it was intended, but its working for me similar to this (I'm using it with Db_NoRecordExists).
Source: Zend_Validate_Db_NoRecordExists example
Sorry for the late reply.
The best option that worked for me is this:
// create an instance of the Zend_Validate_Db_RecordExists class
// pass in the database table name and the first field (as usual)...
$validator = new Zend_Validate_Db_RecordExists(array(
'table' => 'tablename',
'field' => 'first_field'
));
// reset the where clause used by Zend_Validate_Db_RecordExists
$validator->getSelect()->reset('where');
// set again the first field and the second field.
// :value is a named parameter that will be substituted
// by the value passed to the isValid method
$validator->getSelect()->where('first_field = ?', $first_field);
$validator->getSelect()->where('second_field = :value', $second_field);
// add your new record exist based on 2 fields validator to your element.
$element = new Zend_Form_Element_Text('element');
$element->addValidator($validator);
// add the validated element to the form.
$form->addElement($element);
I hope that will help someone :)
Although, I would strongly recommend a neater solution which would be to extend the Zend_Validate_Db_RecordExists class with the above code.
Enjoy!!
Rosario
$dbAdapter = Zend_Db_Table::getDefaultAdapter();
'validators' => array('EmailAddress', $obj= new Zend_Validate_Db_NoRecordExists(array('adapter'=>$dbAdapter,
'field'=>'email',
'table'=>'user',
'exclude'=>array('field'=>'email','value'=>$this->_options['email'], 'field'=>'is_deleted', 'value'=>'1')
))),
For those using Zend 2, If you want to check if user with given id and email exists in table users, It is possible this way.
First, you create the select object that will be use as parameter for the Zend\Validator\Db\RecordExists object
$select = new Zend\Db\Sql\Select();
$select->from('users')
->where->equalTo('id', $user_id)
->where->equalTo('email', $email);
Now, create RecordExists object and check the existence this way
$validator = new Zend\Validator\Db\RecordExists($select);
$validator->setAdapter($dbAdapter);
if ($validator->isValid($username)) {
echo 'This user is valid';
} else {
//get and display errors
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
This sample is from ZF2 official doc
You can use the 'exclude' in this parameter pass the second clause that you want to filter through.
$clause = 'table.field2 = value';
$validator = new Zend_Validate_Db_RecordExists(
array(
'table' => 'table',
'field' => 'field1',
'exclude' => $clause
)
);
if ($validator->isValid('value') {
true;
}
I am using zend framework v.3 and validation via InputFilter(), it uses same validation rules as zend framework 2.
In my case I need to check, if location exists in db (by 'id' field) and has needed company's id ('company_id' field).
I implemented it in next way:
$clause = new Operator('company_id', Operator::OP_EQ, $companyId);
$inputFilter->add([
'name' => 'location_id',
'required' => false,
'filters' => [
['name' => 'StringTrim'],
['name' => 'ToInt'],
],
'validators' => [
[
'name' => 'Int',
],
[
'name' => 'dbRecordExists',
'options' => [
'adapter' => $dbAdapterCore,
'table' => 'locations',
'field' => 'id',
'exclude' => $clause,
'messages' => [
'noRecordFound' => "Location does not exist.",
],
]
],
],
]);
In this case validation will pass, only if 'locations' table has item with columns id == $value and company_id == $companyId, like next:
select * from location where id = ? AND company_id = ?

Lithium mongodb relation between models

I'm using lithium with mongodb and I would like to know with my models below how I could get the data of the user from Posts::find('all'); query?
Do I have to do two queries?
Thanks for your help!
<?php
namespace app\models;
class Posts extends \lithium\data\Model {
protected $_schema = array(
'_id' => array('type' => 'id'),
'name' => array('type' => 'string', 'null' => false),
'description' => array('type' => 'string', 'null' => false),
'created' => array('type' => 'datetime'),
'updated' => array('type' => 'datetime'),
'user_id' => array('type' => 'integer')
);
protected $_meta = array(
'key' => '_id',
);
public $belongsTo = array('Users');
}
?>
<?php
namespace app\models;
class Users extends \lithium\data\Model {
public $hasMany = array('Posts');
public $validates = array(
'name' => 'Please enter a name',
);
protected $_schema = array(
'_id' => array('type' => 'id'),
'name' => array('type' => 'string', 'null' => false),
'slug' => array('type' => 'string', 'null' => false),
'created' => array('type' => 'datetime', 'null' => false),
);
}
?>
Currently relationships only exist for relational databases like MySQL and SQLite3. As such you'll need to make two queries to get the data you want. We're working on adding support for relationships for document based databases now however there currently is no timeframe on that.
You can use Set::extract on your result from posts to pull all of the user id's out then use the results from that to make a single query from users - so from posts you could do $userIDs = Set::extract('/posts/user_id', $posts->data()); then User::find('all', array('conditions' => array('_id' => $userIDs)));
hope this helps.
edit: You can find set::extract information here: http://li3.me/docs/lithium/util/Set::extract()
Do I have to do two queries?
This will depend on your schema.
Case #1
If Users and Posts are two different collections then you will need two different queries.
Case #2
If Users is the top level object and Posts "belongs to" Users then you would do something equivalent to db.users.find({ posts : {$exists:true} }).
I'm not 100% clear on how Lithium handles this. I cannot find a simple example of whether Lithium is doing #1 or #2.
As Howard3 said, there is currently no relationship support for MongoDB and as a result "belongs to" won't work either.
The actual decision depends on your application, but i assume that its going to be some form of a blog (users and posts). According to MongoDB schema design best practices, i'd put both in separate collections because they are "first level collections". a better fit for embedded documents would be posts and comments.
Also, you don't have to define the 'key' stuff when you're on latest master. You can write a custom find method for now that could be easily swapped to the more generic solution when relation support is finished in the core.
if you need more interactive help, visit #li3 on freenode.

Using Zend Framework Db Tables without MVC

I am trying to use the Zend Framework without using the MVC structure, specifically the Db_Table classes.
I have created a couple of classes representing my database tables, i.e.
class DBTables_Templates extends Zend_Db_Table_Abstract
{
protected $_name = "templates";
}
When I try to instantiate this class (it is included fine), I get the following error:
Fatal error: Uncaught exception 'Zend_Db_Table_Exception' with message 'No adapter found for DBTables_Templates'
Does anyone know how I create and include the database adapter for the Db_Table classes to use?
Any pointers are greatly appreciated! I am using the latest version of ZF.
You need to create a Zend_Db_Adapter, which is the class you use to connect to the database.
$db = new Zend_Db_Adapter_Pdo_Mysql(array(
'host' => '127.0.0.1',
'username' => 'webuser',
'password' => 'xxxxxxxx',
'dbname' => 'test'
));
Or you can use the factory() method to make instantiation more configurable:
$db = Zend_Db::factory('Pdo_Mysql', array(
'host' => '127.0.0.1',
'username' => 'webuser',
'password' => 'xxxxxxxx',
'dbname' => 'test'
));
See http://framework.zend.com/manual/en/zend.db.html#zend.db.adapter.connecting
Then specify this adapter object to your table class. There are at least three ways to do this:
Set an application-wide default for all tables:
Zend_Db_Table_Abstract::setDefaultAdapter($db);
Specify the adapter to the table constructor:
$table = new MyTable( array('db'=>$db) );
Store the adapter in the registry and specify it to the table or set it as default:
Zend_Registry::set('my_db', $db);
$table = new MyTable( array('db'=>'my_db') );
// alternatively:
Zend_Db_Table_Abstract::setDefaultAdapter('my_db');
See http://framework.zend.com/manual/en/zend.db.table.html#zend.db.table.constructing