I am using a command controller to import a car list. Now I want to set all the cars which already exists into TYPO3 database to
deleted = 1 before I start the import. After that I want to update only the cars which are on the import list, but this is working only if I run the import command twice and I don't know why.
My importCommand looks as follows:
/**
* import command
*
* #return void
*/
public function importCommand() {
// get all cars from repository
$currentCars = $this->carRepository->findAll();
foreach ($currentCars as $car) {
// mark all cars as deleted
$this->carRepository->remove($car);
}
... read list and store imported data into cars array
foreach ($this->cars as $car) {
// check if car exists
if (!$this->carRepository->exists($car[1])) {
$carObject = $this->objectManager->get(
'Fox\\Example\\Domain\\Model\\Car'
);
$this->carExists = false;
} else {
$carObject = $this->carRepository->getCarByGroupId($car[1])[0];
$this->carExists = true;
}
... set car properties
if (!$this->carExists) {
$this->carRepository->add($carObject);
} else {
$carObject->setDeleted(0);
$this->carRepository->update($carObject);
}
}
}
If I run the command controller via shell and then looking into phpmyadmin I can see that the cars will be updated when I run the import command, but the property deleted will be only updated if I run the import command twice.
You're producing lot of overheat imho... i.e. you can mark all cars with common TYPO3 DB API which works ok even in Extbase repository:
// Delete all
$GLOBALS['TYPO3_DB']->exec_DELETEquery('tx_example_domain_model_car', '1');
// Restore all
$GLOBALS['TYPO3_DB']->exec_UPDATEquery('tx_example_domain_model_car', 'deleted=1', array('deleted' => 0));
On the other hand you definitely should NOT use the deleted field to do this! Instead create a boolean field like hiddenBeforeImport and modify your exists(...) method and set this to true instead removing items, reason is simple, soon you'll not be able to recognize which car is deleted because it was deleted before import, and which is deleted, because somebody clicked thrash icon in the BE. Of course also with custom field DB API methods will be more effective over the Extbase's iteration.
Related
I have the following problem: When I use a Model/Repository with a different mapping, I don't get any property and values.
I've mapped the Repository to fetch the data from table sys_files.
I do get the UID, I also do get the PID. Unfortunately, I do not get any other property or the value.
My Repository is a simple Repository mapped to sys_files.
Unfortunately, I do not get any orther property.
Thanks a lot.
Greetz
Have you defined the mapping in the ext_typoscript_setup.txt?
config.tx_extbase {
persistence {
classes {
Vendor\Package\Domain\Model\MyModel {
mapping {
tableName = sys_file
}
}
}
}
}
You also need to assign the needed fields in your domain model.
namespace Vendor\Package\Domain\Model;
class MyModel
{
/**
* #var string
*/
protected $identifier;
public function getIdentifier()
{
return $this->identifier;
}
public function setIdentifier($identifier)
{
$this->identifier = $identifier;
}
}
There is a checklist when you mapping a model to a table:
1. Create the ext_typoscript_setup.txt file in the extension root path.
There you have to write the following code:
config.tx_extbase{
persistence {
classes {
YourModel.mapping{
table = table_you_want_to_map
}
}
}
}
Avoid to add backslash before model namespace
3. Clear cache from install tool. If nothing happens, then, try to delete the typo3temp/autoload folder.
4. The fields from the model should be camelCase.
Example of field: field_name in your model will be fieldName
5. Check the getters in your model.
Okay, problem solved - almost.
I can't get hash values. I don't know why but it is how it is.
I get the values of each column except "identifier_hash", "folder_hash". These attributes are always NULL.
Now I only have to make a new file_reference record in my db when I add a new relation.
Now I am creating a simple banking project for learning purpose where I need to do a lot of search, update and insert operations for a simple action. For example, if I want to create a transaction from a sample user id, in the "Create Trasaction" Screen, after inputting the details and pressing "submit" button, my application will do the following actions.
1) Insert a row in login session table with values: IP address, user id and timing.
2) To check if the particular user id has access to create a transaction option from user access table.
3) To check if the accounts being debited/credited belong to the same branch code as the home branch code of the creating user.
3) To check if the input inventory (if any) i.e. DD, Cheque is valid or not from inventory table.
4) To check if the account being debited/credited has freeze or not.
5) To check if the account being debited has enough available balance or not.
6) Check the account status Active/Inactive or Dormant.
7) Check and create service tax if applicable i.e. another search from S.Tax table and insert into accounts transaction table
and finally,
8) Insert a row into the accounts transaction table if the criteria pass.
Now I do not feel comfortable to write so many preparedstatement code in my Servlet for only creating a transactions. There will be other operations in my application too. So I was wondering if there is a way we can simply write these SQL statements and pass the SQL file to the Servlet anyway. Or maybe we can write a function in PL/SQL and pass the function to the servelt. Are these ways possible?
Please note, I am using J2EE and Oracle database.
I did this once with a project I was doing some years back and I actually achieved something close to what you are looking for I created a properties file in this format:
trans.getTransactons=select * from whateverTable where onesqlquery
trans.getTranId=select tran_id from whatevertable where anothersqlquery
So that when you write your classes you just load the Properties from the file and the query is populated from the property: for example: This Loads the Property fle
public class QueriesLoader {
Properties prop;
public QueriesLoader() {
}
public Properties getProp() {
prop = new Properties();
ClassLoader classLoader = getClass().getClassLoader();
try {
InputStream url = classLoader.getResourceAsStream("path/to/your/propertiesFile/databasequeries.properties");
prop.load(url);
} catch (IOException asd) {
System.out.println(asd.getMessage());
}
return prop;
}
}
And then in you Database Access Objects
public ArrayList getAllTransactions() {
ArrayList arr = new ArrayList();
try {
String sql = que.getProp().getProperty("trans.getTransactons");
PreparedStatement ps = DBConnection.getDbConnection().prepareStatement(sql);
ResultSet rs = ps.executeQuery();
while (rs.next()) {
arr.add(rs.getString(1));
}
DBConnection.closeConn(DBConnection.getDbConnection());
} catch (IOException asd) {
log.debug(Level.FATAL, asd);
} catch (SQLException asd) {
log.debug(Level.FATAL, asd);
}
return arr;
}
And I ended up not writing a single Query Inside my classes. I hope this Helps you.
I am trying to run a simple query off of the Tx_Extbase_Domain_Repository_FrontendUserRepository. I cannot get anything to work except findByUid(), not even findAll().
In my controller I have this code which seems to work:
/**
* #var Tx_Extbase_Domain_Repository_FrontendUserRepository
*/
protected $userRepository;
/**
* Inject the user repository
* #param Tx_Extbase_Domain_Repository_FrontendUserRepository $userRepository
* #return void */
public function injectFrontendUserRepository(Tx_Extbase_Domain_Repository_FrontendUserRepository $userRepository) {
$this->userRepository = $userRepository;
}
/**
* action create
*
* #param Tx_BpsCoupons_Domain_Model_Coupon $newCoupon
* #return void
*/
public function createAction(Tx_BpsCoupons_Domain_Model_Coupon $newCoupon) {
...... some code .....
$user = $this->userRepository->findByUid(($GLOBALS['TSFE']->fe_user->user[uid]));
$newCoupon->setCreator($user);
...... some code .....
}
but in another function I want to look up a user not by uid but by a fe_users column called vipnumber (an int column) so I tried
/**
* check to see if there is already a user with this vip number in the database
* #param string $vip
* #return bool
*/
public function isVipValid($vip) {
echo "<br/>" . __FUNCTION__ . __LINE__ . "<br/>";
echo "<br/>".$vip."<br/>";
//$ret = $this->userRepository->findByUid(15); //this works!! but
$query = $this->userRepository->createQuery();
$query->matching($query->equals('vip',$vip) );
$ret = $query->execute(); //no luck
.................
and neither does this
$ret = $this->userRepository->findAll();
How can one work but not the others? In my setup I already put
config.tx_extbase.persistence.classes.Tx_Extbase_Domain_Model_FrontendUser.mapping.recordType >
which seems to be necessary for the fiondByUid to work, is i t preventing the other from working?
I am using typo3 v 4.5.30 with extbase 1.3
Thanks
If $this->userRepository->findByUid(15); works, there is no reason why $this->userRepository->findAll(); should not. However $this->userRepository->findAll(); returns not a single Object but a collection of all objects, so you have to iterate over them.
If you add a column to the fe_users, you have to add it to TCA and to your extbase model (you need a getter and a setter), too! After that you can call findByProperty($property) in your repository. In your case that would be
$user = $this->userRepository->findByVipnumber($vip);
This will return all UserObjects that have $vip set as their Vipnumber. If you just want to check if that $vip is already in use, you can call
$user = $this->userRepository->countByVipnumber($vip);
instead. Which obviously returns the number of Users that have this $vip;
You never use $query = $this->createQuery(); outside your Repository.
To add the property to the fronenduser Model you create your own model Classes/Domain/Model/FronendUser.php:
class Tx_MyExt_Domain_Model_FrontendUser extends Tx_Extbase_Domain_Model_FrontendUser {
/**
* #var string/integer
*/
protected $vipnumber;
}
Add a getter and a setter. Now you create your own FrontendUserRepository and extend the extbase one like you did with the model. You use this repository in your Controller. Now you're almost there: Tell Extbase via typoscript, that your model is using the fe_users table and everything should work:
config.tx_extbase {
persistence{
Tx_MyExt_Domain_Model_FrontendUser{
mapping {
tableName = fe_users
}
}
}
}
To disable storagePids in your repository in general, you can use this code inside your repository:
/**
* sets query settings repository-wide
*
* #return void
*/
public function initializeObject() {
$querySettings = $this->objectManager->create('Tx_Extbase_Persistence_Typo3QuerySettings');
$querySettings->setRespectStoragePage(FALSE);
$this->setDefaultQuerySettings($querySettings);
}
After this, your Querys will work for all PIDs.
I didn't have the opportunity to work with frontend users yet, so I don't know if the following applies in this case:
In a custom table I stumbled uppon the fact, that extbase repositories automatically have a look at the pids stored in each entry and check it against a set storage pid (possibly also the current pid if not set). Searching for a uid usually means you have a specific dataset in mind so automatic checks for other values could logically be ignored which would support your experiences. I'd try to set the storage pid for your extension to the place the frontend users are stored in ts-setup:
plugin.[replace_with_extkey].persistence.storagePid = [replace_with_pid]
I have a table which has only two column key-value. I want to create a form which allow user insert 3 pair of key-value settings.
Do I need pass 3 different models to the view? Or is there any possible way to do this?
Check out this link:
http://www.yiiframework.com/doc/guide/1.1/en/form.table
This is considered best form in Yii for updating for creating multiple models.
In essence, for creation you can create a for loop generate as many inputs a you wish to have visible, and in your controller loop over the inputs to create new models.
View File:
for ( $settings as $i=>$setting ) //Settings would be an array of Models (new or otherwise)
{
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::activeLabelEx($setting, "[$i]key");
echo CHtml::error($setting, "[$i]key");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::activeTextField($setting, "[$i]value");
echo CHtml::error($setting, "[$i]value");
}
Controller actionCreate:
$settings = array(new Setting, new Setting, new Setting);
if ( isset( $_POST['Settings'] ) )
foreach ( $settings as $i=>$setting )
if ( isset( $_POST['Setttings'][$i] ) )
{
$setting->attributes = $_POST['Settings'][$i];
$setting->save();
}
//Render View
To update existing models you can use the same method but instead of creating new models you can load models based on the keys in the $_POST['Settings'] array.
To answer your question about passing 3 models to the view, it can be done without passing them, but to validate data and have the correct error messages sent to the view you should pass the three models placed in the array to the view in the array.
Note: The example above should work as is, but does not provide any verification that the models are valid or that they saved correctly
I'm going to give you a heads up and let you know you could potentially make your life very complicated with this.
I'm currently using an EAV patterned table similar to this key-value and here's a list of things you may find difficult or impossible:
use CDbCriteria mergeWith() to filter related elements on "value"s in the event of a search() (or other)
Filtering CGridView or CListView
If this is just very straight forward key-value with no related entity aspect ( which I'm guessing it is since it looks like settings) then one way of doing it would be:
create a normal "Setting" CActiveRecord for your settings table (you will use this to save entries to your settings table)
create a Form model by extending CFormModel and use this as the $model in your form.
Add a save() method to your Form model that would individually insert key-value pairs using the "Setting" model. Preferably using a transaction incase a key-value pair doesn't pass Settings->validate() (if applicable)
optionally you may want to override the Form model's getAttributes() to return db data in the event of a user wanting to edit an entry.
I hope that was clear enough.
Let me give you some basic code setup. Please note that I have not tested this. It should give you a rough idea though.:
Setting Model:
class Setting extends CActiveRecord
{
public function tableName()
{
return 'settings';
}
}
SettingsForm Model:
class SettingsForm extends CFormModel
{
/**
* Load attributes from DB
*/
public function loadAttributes()
{
$settings = Setting::model()->findAll();
$this->setAttributes(CHtml::listData($settings,'key','value'));
}
/*
* Save to database
*/
public function save()
{
foreach($this->attributes as $key => $value)
{
$setting = Setting::model()->find(array('condition'=>'key = :key',
'params'=>array(':key'=>$key)));
if($setting==null)
{
$setting = new Setting;
$setting->key = $key;
}
$setting->value = $value;
if(!$setting->save(false))
return false;
}
return true;
}
}
Controller:
public function actionSettingsForm()
{
$model = new Setting;
$model->loadAttributes();
if(isset($_POST['SettingsForm']))
{
$model->attributes = $_POST['SettingsForm'];
if($model->validate() && $model->save())
{
//success code here, with redirect etc..
}
}
$this->render('form',array('model'=>$model));
}
form view :
$form=$this->beginWidget('CActiveForm', array(
'id'=>'SettingsForm'));
//all your form element here + submit
//(you could loop on model attributes but lets set it up static for now)
//ex:
echo $form->textField($model,'fieldName'); //fieldName = db key
$this->endWidget($form);
If you want further clarification on a point (code etc.) let me know.
PS: for posterity, if other people are wondering about this and EAV they can check the EAV behavior extention or choose a more appropriate DB system such as MongoDb (there are a few extentions out there) or HyperDex
In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);