TYPO3 Extbase repository constraint with MM relation - model-view-controller

I build an extension with a table for items, these can be either projects or objects, a project being a container for multple objects.
To distinguish, a checkbox is used to label an item as project, and when this checkbox is ticked an optional field is displayed.
This field is a relation (m:n) from the project to the objects it contains (same table). The Multiple side by side select displayes only non projects and objects not yet assigned to a project through foreign_table_where.
This field has following TCA:
'objects' => [
'displayCond' => 'FIELD:isproject:=:1',
'exclude' => 0,
'label' => $ll . 'tx_myext_domain_model_item.objects',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_myext_domain_model_item',
'foreign_table_where' => 'AND isproject = 0 AND tx_myext_domain_model_item.uid NOT IN (SELECT uid_foreign FROM tx_myext_item_object_mm WHERE uid_local != ###THIS_UID###)',
'MM' => 'tx_myext_item_object_mm',
'size' => 10,
'autoSizeMax' => 30,
'maxitems' => 9999,
'multiple' => 0
],
],
with my plugin I give the option (trough a flexform) to select to display only objects, only projects or both, done with following code in the repository:
public function findList($entryInclude = 'objects_only') {
/** #var \TYPO3\CMS\Extbase\Persistence\Generic\Query $query */
$query = $this->createQuery();
switch ($entryInclude) {
case 'projects_objects':
$foreign_uids = $this->createQuery()
->statement('SELECT uid_foreign FROM tx_myext_item_object_mm')
->execute();
$constraints = [
$query->equals('isproject', 1),
$query->logicalNot($query->in('uid', $foreign_uids))
];
break;
case 'projects_only':
$constraints = $query->equals('isproject', 1);
break;
default:
$constraints = $query->equals('isproject', 0);
break;
}
$query->matching($query->logicalAnd($constraints));
return $query->execute();
}
the effort to build an array of all uid_foreign found in the tx_myext_item_object_mm table causes an error ...

This is, in my oppinion, a case for a custom query. The following should give you the objects not referenced to from within your mm-table:
$query = $this->createQuery();
$query->statement('
SELECT *
FROM tx_myext_domain_model_item
WHERE uid NOT IN(
SELECT foreign_uid FROM tx_myext_item_object_mm
)
');
$query->execute();

Related

Laravel backpack select2_from_ajax setting my value as null after the correct value has been saved

Im having a weird problem.
Im using laravel backpack for an admin panel. There i use select2_from_ajax to list a values according to another field in create operation. It is showing up correctly as expected & i can select one too.
But after selection when i click save & back it gives me an error
That means my column doesn't allow to update to null right.
So when i go back & check the column it has saved the correct value.
But when default value of my column was null this error will not showup & db value would be changed to null.
This is my select2_from_ajax part.
$this->crud->addField([ // Select
'label' => "Link Type",
'type' => 'select_from_array',
'name' => 'link_type', // the db column for the foreign key
'options' => [1 => 'Product',0 => 'Collection'],
'allows_null' => false,
]);
$this->crud->addField([ // Select
'label' => "Link To", // Table column heading
'type' => "select2_from_ajax",
'name' => "link_to",
'entity' => 'link',
'attribute' => "name",
'data_source' => url('admin/itemtype'),
'placeholder' => "Select a item",
'minimum_input_length' => 0,
'include_all_form_fields' => true,
'dependencies' => ['link_type'],
]);
So why is it trying to set null value after the correct value?
Any help would be appreciated. Thanks.
My admin/itemtype function:
$search_term = $request->input('q');
$form = collect($request->input('form'))->pluck('value', 'name');
if ($search_term) {
if ($form['link_type'] == 0) {
$items = Collection::where('name', 'LIKE', '%' . $search_term . '%')->paginate(10);
} else {
$items = Product::where('title', 'LIKE', '%' . $search_term . '%')->paginate(10);
}
} else {
if ($form['link_type'] == 0) {
$items = Collection::paginate(10);
} else {
$items = Product::paginate(10);
}
}
return $items;

Laravel insert or update multiple rows

Im new in laravel, and im trying to update my navigation tree.
So i want to update my whole tree in one query without foreach.
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
);
I just want to ask - is there posibility in laravel to insert(if new in array) or update my current rows in database?
I want to update all, because i have fields _lft, _right, parent_id in my tree and im using some dragable js plugin to set my navigation structure - and now i want to save it.
I tried to use
Navigation::updateOrCreate(array(array('id' => '3'), array('id'=>'4')), array(array('name' => 'test11'), array('name' => 'test22')));
But it works just for single row, not multiple like i tried to do.
Maybe there is another way to do it?
It's now available in Laravel >= 8.x
The method's first argument consists of the values to insert or update, while the second argument lists the column(s) that uniquely identify records within the associated table. The method's third and final argument is an array of columns that should be updated if a matching record already exists in the database:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], ['departure', 'destination'], ['price']);
I wonder why this kind of feature is not yet available in Laravel core (till today). Check out this gist The result of the query string would look like this: here
I am putting the code here just in case the link breaks in the future, I am not the author:
/**
* Mass (bulk) insert or update on duplicate for Laravel 4/5
*
* insertOrUpdate([
* ['id'=>1,'value'=>10],
* ['id'=>2,'value'=>60]
* ]);
*
*
* #param array $rows
*/
function insertOrUpdate(array $rows){
$table = \DB::getTablePrefix().with(new self)->getTable();
$first = reset($rows);
$columns = implode( ',',
array_map( function( $value ) { return "$value"; } , array_keys($first) )
);
$values = implode( ',', array_map( function( $row ) {
return '('.implode( ',',
array_map( function( $value ) { return '"'.str_replace('"', '""', $value).'"'; } , $row )
).')';
} , $rows )
);
$updates = implode( ',',
array_map( function( $value ) { return "$value = VALUES($value)"; } , array_keys($first) )
);
$sql = "INSERT INTO {$table}({$columns}) VALUES {$values} ON DUPLICATE KEY UPDATE {$updates}";
return \DB::statement( $sql );
}
So you can safely have your arrays inserted or updated as:
insertOrUpdate(
array(
array('id'=>1, 'name'=>'some navigation point', 'parent'='0'),
array('id'=>2, 'name'=>'some navigation point', 'parent'='1'),
array('id'=>3, 'name'=>'some navigation point', 'parent'='1')
)
);
Just in case any trouble with the first line in the function you can simply add a table name as a second argument, then comment out the line i.e:
function insertOrUpdate(array $rows, $table){
.....
}
insertOrUpdate(myarrays,'MyTableName');
NB: Be careful though to sanitise your input! and remember the timestamp fields are not touched. you can do that by adding manually to each arrays in the main array.
I've created an UPSERT package for all databases: https://github.com/staudenmeir/laravel-upsert
DB::table('navigation')->upsert(
[
['id' => 1, 'name' => 'some navigation point', 'parent' => '0'],
['id' => 2, 'name' => 'some navigation point', 'parent' => '1'],
['id' => 3, 'name' => 'some navigation point', 'parent' => '1'],
],
'id'
);
Eloquent Style
public function meta(){ // in parent models.
return $this->hasMany('App\Models\DB_CHILD', 'fk_id','local_fk_id');
}
.
.
.
$parent= PARENT_DB::findOrFail($id);
$metaData= [];
foreach ($meta['meta'] as $metaKey => $metaValue) {
if ($parent->meta()->where([['meta_key', '=',$metaKey]] )->exists()) {
$parent->meta()->where([['meta_key', '=',$metaKey]])->update(['meta_value' => $metaValue]);
}else{
$metaData[] = [
'FK_ID'=>$fkId,
'meta_key'=>$metaKey,
'meta_value'=> $metaValue
];
}
}
$Member->meta()->insert($metaData);
No, you can't do this. You can insert() multiple rows at once and you can update() multiple rows using same where() condition, but if you want to use updateOrCreate(), you'll need to use foreach() loop.
I didn't find a way to bulk insert or update in one query. But I have managed with only 3 queries. I have one table name shipping_costs. Here I want to update the shipping cost against the shipping area. I have only 5 columns in this table id, area_id, cost, created_at, updated_at.
// first get ids from table
$exist_ids = DB::table('shipping_costs')->pluck('area_id')->toArray();
// get requested ids
$requested_ids = $request->get('area_ids');
// get updatable ids
$updatable_ids = array_values(array_intersect($exist_ids, $requested_ids));
// get insertable ids
$insertable_ids = array_values(array_diff($requested_ids, $exist_ids));
// prepare data for insert
$data = collect();
foreach ($insertable_ids as $id) {
$data->push([
'area_id' => $id,
'cost' => $request->get('cost'),
'created_at' => now(),
'updated_at' => now()
]);
}
DB::table('shipping_costs')->insert($data->toArray());
// prepare for update
DB::table('shipping_costs')
->whereIn('area_id', $updatable_ids)
->update([
'cost' => $request->get('cost'),
'updated_at' => now()
]);
in your controller
use DB;
public function arrDta(){
$up_or_create_data=array(
array('id'=>2, 'name'=>'test11'),
array('id'=>4, 'name'=>'test22')
);
var_dump($up_or_create_data);
echo "fjsdhg";
foreach ($up_or_create_data as $key => $value) {
echo "key ".$key;
echo "<br>";
echo " id: ".$up_or_create_data[$key]["id"];
echo "<br>";
echo " Name: ".$up_or_create_data[$key]["name"];
if (Navigation::where('id', '=',$up_or_create_data[$key]["id"])->exists()) {
DB::table('your_table_ name')->where('id',$up_or_create_data[$key]["id"])->update(['name' => $up_or_create_data[$key]["name"]]);
}else{
DB::insert('insert into your_table_name (id, name) values (?, ?)', [$up_or_create_data[$key]["id"], $up_or_create_data[$key]["name"]]);
}
}

Codeigniter Update Multiple Records with different values

I'm trying to update a certain field with different values for different records.
If I were to use MySql syntax, I think it should have been:
UPDATE products
SET price = CASE id
WHEN '566423' THEN 49.99
WHEN '5681552' THEN 69.99
END
WHERE code IN ('566423','5681552');
But I prefer to use Active Record if it's possible.
My input is a tab delimited text which I convert into an array of the id and the desired value for each record:
$data = array(
array(
'id' => '566423' ,
'price' => 49.99
),
array(
'id' => '5681552' ,
'price' => 69.99
)
);
I thought this is the proper structure for update_batch, but it fails. Here's what I've tried:
function updateMultiple()
{
if($this->db->update_batch('products', $data, 'id'))
{
echo "updated";
}
else
{
echo "failed )-:";
}
}
And I get failed all the time. What am I missing?

Filtering a text-type column with MySQL-computed values in Magento Admin Grid

Say one column in grid has computed values:
setCollection():
'refunded' => new Zend_Db_Expr("IF(qty_refunded > 0, 'Yes', 'No')"),
_prepareColumns():
$this->addColumnAfter('refunded', array(
'header' => Mage::helper('helper')->__('Refunded'),
'index' => 'refunded',
'type' => 'text',
), 'qty');
What and how must one change in order to have columns with "yes" values, in case admin types "yes" then filters?
Adminhtml grid columns have a filter property which specifies a block class. For boolean yes/no fields that would usually be adminhtml/widget_grid_column_filter_select.
It would be used automatically if your field type would be 'options'.
Try this in _prepareCollection():
'refunded' => new Zend_Db_Expr("IF(qty_refunded > 0, 1, 0)"),
And in _prepareColumns() use:
$this->addColumnAfter('refunded', array(
'header' => Mage::helper('helper')->__('Refunded'),
'index' => 'refunded',
'type' => 'options',
'options' => array(0 => $this->__('No'), 1 => $this->__('Yes'))
), 'qty');
This should still render your values as "Yes" and "No" in the Column, and you would get the select with the appropriate options as a filter dropdown.
This alone won't be enough since the column with the computed value can't be referenced directly in the WHERE clause by MySQL. Magento provides two options to work around that.
Column filter blocks have a method getCondition() which return a condition that will be used to filter the collection. See Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Abstract::getCondition() for an example.
So if you need to customize the SQL used to execute the filter, create your own column filter block extending Mage_Adminhtml_Block_Widget_Grid_Column_Filter_Select and adjust the returned condition as needed, i.e. use the same computed value to match against.
Your custom filter can be specified for the column like this in the addColumn() definition:
'type' => 'options',
'options' => array(0 => $this->__('No'), 1 => $this->__('Yes')),
'filter' => 'your_module/adminhtml_widget_grid_column_filter_custom',
If you prefere to work outside of the limitations of Magento's ORM filter syntax, you can modify the collections select directly by using a filter callback:
'type' => 'options',
'options' => array(0 => $this->__('No'), 1 => $this->__('Yes')),
'filter_condition_callback' => array($this, '_applyMyFilter'),
The callback receives the collection and the column as arguments. Here is a simple example for that method:
protected function _applyMyFilter(Varien_Data_Collection_Db $collection, Mage_Adminhtml_Block_Widget_Grid_Column $column)
{
$select = $collection->getSelect();
$field = $column->getIndex();
$value = $column->getFilter()->getValue();
$select->having("$field=?, $value);
}
Needless to say that both approaches (filtering against the computed value) is very inefficient in MySQL. But maybe that's no a problem for you in this case.
I'll post a working example, but I'll choose Vinai's answer for being so detailed.
In Grid.php:
protected function _addColumnFilterToCollection($column)
{
if ($column->getId() == 'refunded' && $column->getFilter()->getValue()) {
$val = $column->getFilter()->getValue();
$comparison = ($val === "No") ? 'lteq' : 'gt'; // lteg: <=; gt: >
$this->getCollection()->addFieldToFilter('ois.qty_refunded', array($comparison => 0));
} else {
parent::_addColumnFilterToCollection($column);
}
return $this;
}

Add an auto_increment column in Magento setup script without using SQL

Previously I asked how to ALTER TABLE in Magento setup script without using SQL. There, Ivan gave an excellent answer which I still refer to even now.
However I have yet to discover how to use Varien_Db_Ddl_Table::addColumn() to specify an auto_increment column. I think it has something to do with an option called identity but so far have had no luck.
Is this even possible or is that functionality incomplete?
One can create an autoincrement column like that (at least since Magento 1.6, maybe even earlier):
/** #var $table Varien_Db_Ddl_Table */
$table->addColumn( 'id', Varien_Db_Ddl_Table::TYPE_INTEGER, null, array(
'auto_increment' => true,
'unsigned' => true,
'nullable' => false,
'primary' => true,
), 'ID' );
Instead of "auto_increment", one may also use the keyword "identity".
I think that's something that hasn't been implemented yet.
If you look at the source to addColumn, you can see it looks for a identity/auto_increment option and sets an IDENTITY attribute on the internal column representation.
#File: lib/Varien/Db/Ddl/Table.php
if (!empty($options['identity']) || !empty($options['auto_increment'])) {
$identity = true;
}
$upperName = strtoupper($name);
$this->_columns[$upperName] = array(
'COLUMN_NAME' => $name,
'COLUMN_TYPE' => $type,
'COLUMN_POSITION' => $position,
'DATA_TYPE' => $type,
'DEFAULT' => $default,
'NULLABLE' => $nullable,
'LENGTH' => $length,
'SCALE' => $scale,
'PRECISION' => $precision,
'UNSIGNED' => $unsigned,
'PRIMARY' => $primary,
'PRIMARY_POSITION' => $primaryPosition,
'IDENTITY' => $identity
);
However, if you look at the createTable method on the connection object
#File: lib/Varien/Db/Adapter/Pdo/Mysql.php
public function createTable(Varien_Db_Ddl_Table $table)
{
$sqlFragment = array_merge(
$this->_getColumnsDefinition($table),
$this->_getIndexesDefinition($table),
$this->_getForeignKeysDefinition($table)
);
$tableOptions = $this->_getOptionsDefination($table);
$sql = sprintf("CREATE TABLE %s (\n%s\n) %s",
$this->quoteIdentifier($table->getName()),
implode(",\n", $sqlFragment),
implode(" ", $tableOptions));
return $this->query($sql);
}
you can see _getColumnsDefinition, _getIndexesDefinition, and _getForeignKeysDefinition are used to create a CREATE SQL fragment. None of these methods make any reference to identity or auto_increment, nor do they appear to generate any sql that would create an auto increment.
The only possible candidates in this class are
/**
* Autoincrement for bind value
*
* #var int
*/
protected $_bindIncrement = 0;
which is used to control the increment number for a PDO bound parameter (nothing to do with auto_increment).
There's also a mention of auto_increment here
protected function _getOptionsDefination(Varien_Db_Ddl_Table $table)
{
$definition = array();
$tableProps = array(
'type' => 'ENGINE=%s',
'checksum' => 'CHECKSUM=%d',
'auto_increment' => 'AUTO_INCREMENT=%d',
'avg_row_length' => 'AVG_ROW_LENGTH=%d',
'comment' => 'COMMENT=\'%s\'',
'max_rows' => 'MAX_ROWS=%d',
'min_rows' => 'MIN_ROWS=%d',
'delay_key_write' => 'DELAY_KEY_WRITE=%d',
'row_format' => 'row_format=%s',
'charset' => 'charset=%s',
'collate' => 'COLLATE=%s'
);
foreach ($tableProps as $key => $mask) {
$v = $table->getOption($key);
if (!is_null($v)) {
$definition[] = sprintf($mask, $v);
}
}
return $definition;
}
but this is used to process options set on the table. This auto_increment controls the table AUTO_INCREMENT options, which can be used to control which integer an AUTO_INCREMENT starts at.

Resources