From 36d6b75a0e8a20b33299cc87558f9f19439ee214 Mon Sep 17 00:00:00 2001 From: Taras Machyshyn Date: Thu, 26 Dec 2013 11:39:33 +0200 Subject: [PATCH 1/2] orm convertation changes, added 'relationName' for entityDefs (uses fot Team), added currency field as float type --- .../Espo/Core/Utils/Database/Converter.php | 5 +- .../Core/Utils/Database/Converters/Links.php | 8 +- .../Core/Utils/Database/Converters/Orm.php | 73 +++++++++++++++---- .../Utils/Database/Converters/Relations.php | 16 ++-- .../Core/Utils/Database/Converters/Schema.php | 7 +- .../metadata/entityDefs/Account.json | 3 +- .../Resources/metadata/entityDefs/Call.json | 3 +- .../Resources/metadata/entityDefs/Case.json | 3 +- .../metadata/entityDefs/Contact.json | 3 +- .../Resources/metadata/entityDefs/Email.json | 3 +- .../metadata/entityDefs/InboundEmail.json | 3 +- .../Resources/metadata/entityDefs/Lead.json | 3 +- .../metadata/entityDefs/Meeting.json | 3 +- .../metadata/entityDefs/Opportunity.json | 3 +- .../metadata/entityDefs/Prospect.json | 3 +- .../Resources/metadata/entityDefs/Task.json | 3 +- .../metadata/entityDefs/EmailTemplate.json | 3 +- .../Resources/metadata/entityDefs/Role.json | 3 +- .../Resources/metadata/fields/currency.json | 3 + .../Espo/Resources/metadata/fields/date.json | 3 + .../Resources/metadata/fields/datetime.json | 3 + 21 files changed, 116 insertions(+), 41 deletions(-) diff --git a/application/Espo/Core/Utils/Database/Converter.php b/application/Espo/Core/Utils/Database/Converter.php index 07ced0812e..9e30c0ff25 100644 --- a/application/Espo/Core/Utils/Database/Converter.php +++ b/application/Espo/Core/Utils/Database/Converter.php @@ -67,7 +67,7 @@ class Converter */ public function process() { - $GLOBALS['log']->add('Debug', 'Converter:process() - Start: converting metadata to orm format and database schema'); + $GLOBALS['log']->add('Debug', 'Converters\Orm - Start: orm convertation'); $entityDefs = $this->getMetadata()->get('entityDefs'); @@ -87,8 +87,7 @@ class Converter //save database meta to a file espoMetadata.php $result = $this->getMetadata()->setOrmMetadata($databaseMeta); - - $GLOBALS['log']->add('Debug', 'Converter:process() - End: converting metadata to orm format and database schema, result=['.$result.']'); + $GLOBALS['log']->add('Debug', 'Converters\Orm - End: orm convertation, result=['.$result.']'); return $result; } diff --git a/application/Espo/Core/Utils/Database/Converters/Links.php b/application/Espo/Core/Utils/Database/Converters/Links.php index e91a14d0e1..9ad624b54e 100644 --- a/application/Espo/Core/Utils/Database/Converters/Links.php +++ b/application/Espo/Core/Utils/Database/Converters/Links.php @@ -63,16 +63,16 @@ class Links $params['targetEntity'] = $foreignParams['entityName']; $foreignParams['targetEntity'] = $params['entityName']; - //hardcode for Teams + //hasMany With Relation Name if (isset($link['params'])) { switch ($link['params']['type']) { case 'hasMany': - if ($params['targetEntity'] == 'Team') { - $method = 'teamRelation'; + if (isset($link['params']['relationName'])) { + $method = 'hasManyWithName'; } break; } - } //END: hardcode + } //END: hasMany With Relation Name if (method_exists($this, $method)) { return $this->$method($params, $foreignParams); diff --git a/application/Espo/Core/Utils/Database/Converters/Orm.php b/application/Espo/Core/Utils/Database/Converters/Orm.php index 5e12b77437..e5bc5ced9e 100644 --- a/application/Espo/Core/Utils/Database/Converters/Orm.php +++ b/application/Espo/Core/Utils/Database/Converters/Orm.php @@ -28,7 +28,9 @@ class Orm */ protected $fieldAccordances = array( 'type' => 'type', + 'dbType' => 'dbType', 'maxLength' => 'len', + 'len' => 'len', 'autoincrement' => 'autoincrement', 'notStorable' => 'notStorable', 'link' => 'relation', @@ -107,10 +109,6 @@ class Orm $fieldParams['len'] = $this->defaultLength['varchar']; break; - case 'datetime': - $fieldParams['notnull'] = false; - break; - case 'bool': $fieldParams['default'] = isset($fieldParams['default']) ? (bool) $fieldParams['default'] : $this->defaultValue['bool']; break; @@ -150,22 +148,21 @@ class Orm //check if "fields" option exists in $fieldMeta $fieldParams['type'] = isset($fieldParams['type']) ? $fieldParams['type'] : ''; - $fieldTypeMeta = $this->getMetadata()->get('fields.'.$fieldParams['type']); + if (isset($fieldTypeMeta['fields']) && is_array($fieldTypeMeta['fields'])) { - $namingType = isset($fieldTypeMeta['naming']) ? $fieldTypeMeta['naming'] : $this->defaultNaming; - foreach($fieldTypeMeta['fields'] as $subFieldName => $subFieldParams) { + foreach($fieldTypeMeta['actualFields'] as $subFieldName) { - //$subFieldNameNaming = Util::fromCamelCase( Util::getNaming($fieldName, $subFieldName, $namingType, '_'), '_' ); - $subFieldNameNaming = Util::getNaming($fieldName, $subFieldName, $namingType); - if (!isset($entityMeta['fields'][$subFieldNameNaming])) { - $subFieldDefs = $this->convertField($entityName, $subFieldName, $subFieldParams); + $subField = $this->convertActualFields($entityName, $fieldName, $fieldParams, $subFieldName, $fieldTypeMeta); + + //if (!isset($entityMeta['fields'][ $subField['naming'] ])) { + if (!isset($outputMeta[ $subField['naming'] ])) { + $subFieldDefs = $this->convertField($entityName, $subField['name'], $subField['params']); if ($subFieldDefs !== false) { - $outputMeta[$subFieldNameNaming] = $subFieldDefs; //push fieldDefs to the main array + $outputMeta[ $subField['naming'] ] = $subFieldDefs; //push fieldDefs to the main array } } - } } else { @@ -214,7 +211,11 @@ class Orm $fieldParams['type'] = $this->defaultFieldType; } //END: set default type if exists - $fieldTypeMeta = $this->getMetadata()->get('fields.'.$fieldParams['type']); + if (isset($fieldParams['dbType'])) { + $fieldTypeMeta = $this->getMetadata()->get('fields.'.$fieldParams['dbType']); + } else { + $fieldTypeMeta = $this->getMetadata()->get('fields.'.$fieldParams['type']); + } //check if need to skip this field into database metadata if (isset($fieldTypeMeta['database']['skip']) && $fieldTypeMeta['database']['skip'] === true) { @@ -242,6 +243,50 @@ class Orm return $fieldDefs; } + protected function convertActualFields($entityName, $fieldName, $fieldParams, $subFieldName, $fieldTypeMeta) + { + $subField = array(); + + $subField['params'] = $this->getInitValues($fieldParams); + + //if empty field name, then use the main field + if (trim($subFieldName) == '') { + + if (!isset($fieldTypeMeta['database'])) { + $GLOBALS['log']->add('EXCEPTION', 'Empty field defs for ['.$entityName.':'.$fieldName.'] using "actualFields". Main field ['.$fieldName.']'); + } + + $subField['name'] = $fieldName; + $subField['naming'] = $fieldName; + if (isset($fieldTypeMeta['database'])) { + $subField['params'] = Util::merge($subField['params'], $fieldTypeMeta['database']); + } + + } else { + + if (!isset($fieldTypeMeta['fields'][$subFieldName])) { + $GLOBALS['log']->add('EXCEPTION', 'Empty field defs for ['.$entityName.':'.$subFieldName.'] using "actualFields". Main field ['.$fieldName.']'); + } + + $namingType = isset($fieldTypeMeta['naming']) ? $fieldTypeMeta['naming'] : $this->defaultNaming; + + $subField['name'] = $subFieldName; + $subField['naming'] = Util::getNaming($fieldName, $subFieldName, $namingType); + if (isset($fieldTypeMeta['fields'][$subFieldName])) { + $subField['params'] = Util::merge($subField['params'], $fieldTypeMeta['fields'][$subFieldName]); + } + + } + + /* + name = $subFieldName + naming = $subFieldNameNaming + params = $subFieldParams + */ + + return $subField; + } + protected function convertLinks($entityName, $entityMeta, array $entityDefs) diff --git a/application/Espo/Core/Utils/Database/Converters/Relations.php b/application/Espo/Core/Utils/Database/Converters/Relations.php index 78b3eb0dd6..b28501c1d0 100644 --- a/application/Espo/Core/Utils/Database/Converters/Relations.php +++ b/application/Espo/Core/Utils/Database/Converters/Relations.php @@ -217,19 +217,23 @@ class Relations - public function teamRelation($params, $foreignParams) + //public function teamRelation($params, $foreignParams) + public function hasManyWithName($params, $foreignParams) { + $relationKeys = explode('-', Util::fromCamelCase($params['link']['params']['relationName'])); + $midKeys = array(); + foreach($relationKeys as $key) { + $midKeys[] = $key.'Id'; + } + return array( $params['entityName'] => array( 'relations' => array( $params['link']['name'] => array( 'type' => Entity::MANY_MANY, 'entity' => $params['targetEntity'], - 'relationName' => 'EntityTeam', - 'midKeys' => array( - 'entityId', - 'teamId', - ), + 'relationName' => $params['link']['params']['relationName'], + 'midKeys' => $midKeys, 'conditions' => array('entityType' => $params['entityName']), ), ), diff --git a/application/Espo/Core/Utils/Database/Converters/Schema.php b/application/Espo/Core/Utils/Database/Converters/Schema.php index 8eab0cb64b..674d85ab09 100644 --- a/application/Espo/Core/Utils/Database/Converters/Schema.php +++ b/application/Espo/Core/Utils/Database/Converters/Schema.php @@ -54,7 +54,9 @@ class Schema //convertToSchema public function process(array $databaseMeta, $entityDefs) - { + { + $GLOBALS['log']->add('Debug', 'Converters\Schema - Start: building schema'); + $schema = $this->getSchema(); $tables = array(); @@ -145,6 +147,9 @@ class Schema } //END: check and create columns/tables for relations + + $GLOBALS['log']->add('Debug', 'Converters\Schema - End: building schema'); + return $schema; } diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json index 4b8cd06f1a..a0fe288fa4 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Account.json @@ -107,7 +107,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "contacts": { "type": "hasMany", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json index 3ccf4f42e9..53d2c9ecb5 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Call.json @@ -88,7 +88,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "users": { "type": "hasMany", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json index cf04d7be2a..13fc2069de 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Case.json @@ -70,7 +70,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "account": { "type": "belongsTo", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json index 6b69575900..fde9d807e1 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Contact.json @@ -107,7 +107,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "account": { "type": "belongsTo", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Email.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Email.json index d67c5ad559..9b54dd9e52 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Email.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Email.json @@ -76,7 +76,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "attachments": { "type": "hasChildren", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/InboundEmail.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/InboundEmail.json index 8c1ff25661..fa0656d7bd 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/InboundEmail.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/InboundEmail.json @@ -83,7 +83,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "caseEmailTemplate": { "type": "belongsTo", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json index 6eca004966..c088e53c23 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Lead.json @@ -134,7 +134,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "opportunities": { "type": "hasMany", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json index efc3492269..ca8bafeb98 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Meeting.json @@ -83,7 +83,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "users": { "type": "hasMany", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json index 7573b78b5e..b022ce5be3 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Opportunity.json @@ -75,7 +75,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "account": { "type": "belongsTo", diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Prospect.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Prospect.json index 54bdfb1b86..575e3db06a 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Prospect.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Prospect.json @@ -108,7 +108,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" } }, "collection": { diff --git a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json index 6747d369d4..f9a9098407 100644 --- a/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json +++ b/application/Espo/Modules/Crm/Resources/metadata/entityDefs/Task.json @@ -72,7 +72,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "parent": { "type": "belongsToParent", diff --git a/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json b/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json index 089955a3f2..04ea889860 100644 --- a/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json +++ b/application/Espo/Resources/metadata/entityDefs/EmailTemplate.json @@ -50,7 +50,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" }, "assignedUser": { "type": "belongsTo", diff --git a/application/Espo/Resources/metadata/entityDefs/Role.json b/application/Espo/Resources/metadata/entityDefs/Role.json index ef9ea70bed..3334854676 100644 --- a/application/Espo/Resources/metadata/entityDefs/Role.json +++ b/application/Espo/Resources/metadata/entityDefs/Role.json @@ -15,7 +15,8 @@ }, "teams": { "type": "hasMany", - "entity": "Team" + "entity": "Team", + "relationName": "EntityTeam" } }, "collection": { diff --git a/application/Espo/Resources/metadata/fields/currency.json b/application/Espo/Resources/metadata/fields/currency.json index 5ec25cf428..5f1461f5f7 100644 --- a/application/Espo/Resources/metadata/fields/currency.json +++ b/application/Espo/Resources/metadata/fields/currency.json @@ -27,5 +27,8 @@ "search":{ "basic":false, "advanced":true + }, + "database":{ + "type":"float" } } diff --git a/application/Espo/Resources/metadata/fields/date.json b/application/Espo/Resources/metadata/fields/date.json index 2b6966f617..bd314941d4 100644 --- a/application/Espo/Resources/metadata/fields/date.json +++ b/application/Espo/Resources/metadata/fields/date.json @@ -21,5 +21,8 @@ "search":{ "basic":false, "advanced":true + }, + "database":{ + "notnull":false } } diff --git a/application/Espo/Resources/metadata/fields/datetime.json b/application/Espo/Resources/metadata/fields/datetime.json index 71ab7dca68..2089e6d64a 100644 --- a/application/Espo/Resources/metadata/fields/datetime.json +++ b/application/Espo/Resources/metadata/fields/datetime.json @@ -21,5 +21,8 @@ "search":{ "basic":false, "advanced":false + }, + "database":{ + "notnull":false } } From 842cddb0b1398cce76618f8b98483330c5eaab9d Mon Sep 17 00:00:00 2001 From: Taras Machyshyn Date: Thu, 26 Dec 2013 15:18:55 +0200 Subject: [PATCH 2/2] add to ormMetadata 'select', 'where', 'orderBy' options for personName type --- .../Core/Utils/Database/Converters/Orm.php | 30 ++++++++++----- .../Utils/Database/Converters/Relations.php | 37 +++++++++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/application/Espo/Core/Utils/Database/Converters/Orm.php b/application/Espo/Core/Utils/Database/Converters/Orm.php index e5bc5ced9e..37bce5096f 100644 --- a/application/Espo/Core/Utils/Database/Converters/Orm.php +++ b/application/Espo/Core/Utils/Database/Converters/Orm.php @@ -31,10 +31,12 @@ class Orm 'dbType' => 'dbType', 'maxLength' => 'len', 'len' => 'len', + 'notnull' => 'notnull', 'autoincrement' => 'autoincrement', 'notStorable' => 'notStorable', 'link' => 'relation', 'field' => 'foreign', //todo change "foreign" to "field" + 'unique' => 'unique', 'default' => array( 'condition' => '^javascript:', 'conditionEquals' => false, @@ -112,6 +114,12 @@ class Orm case 'bool': $fieldParams['default'] = isset($fieldParams['default']) ? (bool) $fieldParams['default'] : $this->defaultValue['bool']; break; + + case 'personName': + $fieldParams['type'] = Entity::VARCHAR; + $typeDefs = $this->getLinks()->process('typePersonName', $entityName, array('name' => $fieldName)); + $fieldParams = Util::merge($fieldParams, $typeDefs[$entityName]['fields'][$fieldName]); + break; } } } @@ -139,7 +147,7 @@ class Orm 'dbType' => 'varchar', ), 'name' => array( - 'type' => Entity::VARCHAR, + 'type' => isset($entityMeta['fields']['name']['type']) ? $entityMeta['fields']['name']['type'] : Entity::VARCHAR, 'notStorable' => true, ), ); @@ -222,6 +230,14 @@ class Orm return false; } + //merge database options from field definition + if (isset($fieldTypeMeta['database'])) { + $fieldParams = Util::merge($fieldParams, $fieldTypeMeta['database']); + } + + //if (isset($fieldTypeMeta['database']['notnull'])) + + $fieldDefs = $this->getInitValues($fieldParams); //check if field need to be saved in database @@ -230,10 +246,10 @@ class Orm $fieldDefs['notStorable'] = true; } //END: check if field need to be saved in database - //merge database options from field definition - if (isset($fieldTypeMeta['database'])) { + //merge database options from field definition + /*if (isset($fieldTypeMeta['database'])) { $fieldDefs = Util::merge($fieldDefs, $fieldTypeMeta['database']); - } + }*/ //check and set a field length if (!isset($fieldDefs['len']) && in_array($fieldDefs['type'], array_keys($this->defaultLength))) { @@ -297,12 +313,8 @@ class Orm $relationships = array(); foreach($entityMeta['links'] as $linkName => $linkParams) { - //echo $linkName.'
'; - //print_r($linkParams); $linkEntityName = $this->getLinks()->getLinkEntityName($entityName, $linkParams); - //print_r($entityDefs[$linkEntityName]['links']); - //print_r($convertedMeta[$linkEntityName]); $currentType = $linkParams['type']; $parentType = ''; @@ -391,7 +403,7 @@ class Orm $values = array(); foreach($this->fieldAccordances as $espoType => $doctrineType) { - if (isset($fieldParams[$espoType]) && !empty($fieldParams[$espoType])) { + if (isset($fieldParams[$espoType])) { if (is_array($doctrineType)) { diff --git a/application/Espo/Core/Utils/Database/Converters/Relations.php b/application/Espo/Core/Utils/Database/Converters/Relations.php index b28501c1d0..2be7c86e1a 100644 --- a/application/Espo/Core/Utils/Database/Converters/Relations.php +++ b/application/Espo/Core/Utils/Database/Converters/Relations.php @@ -216,6 +216,43 @@ class Relations } + public function typePersonName($params, $foreignParams) + { + $foreignField = $this->getForeignField($params['link']['name'], $params['entityName']); + $tableName = Util::toUnderScore($params['entityName']); + + $fullList = array(); //contains empty string (" ") like delimiter + $fieldList = array(); //doesn't contain empty string (" ") like delimiter + $like = array(); + foreach($foreignField as $fieldName) { + + $fieldNameTrimmed = trim($fieldName); + if (!empty($fieldNameTrimmed)) { + $columnName = $tableName.'.'.Util::toUnderScore($fieldNameTrimmed); + + $fullList[] = $fieldList[] = $columnName; + $like[] = $columnName." LIKE '{text}'"; + } else { + $fullList[] = "'".$fieldName."'"; + } + } + + return array( + $params['entityName'] => array ( + 'fields' => array( + $params['link']['name'] => array( + 'select' => "TRIM(CONCAT(".implode(", ", $fullList)."))", + 'where' => array( + 'LIKE' => "(".implode(" OR ", $like)." OR CONCAT(".implode(", ", $fullList).") LIKE '{text}')", + ), + 'orderBy' => implode(", ", $fieldList), + ), + ), + ), + ); + } + + //public function teamRelation($params, $foreignParams) public function hasManyWithName($params, $foreignParams)