1 <?xml version="1.0" encoding="UTF-8"?>
2 <!-- EN-Revision: 17598 -->
4 <sect1 id="zend.db.table.relationships">
6 <title>Zend_Db_Table Relationships</title>
8 <sect2 id="zend.db.table.relationships.introduction">
10 <title>Introduction</title>
13 Tables have relationships to each other in a relational database. An entity in one
14 table can be linked to one or more entities in another table by using referential
15 integrity constraints defined in the database schema.
20 <classname>Zend_Db_Table_Row</classname>
21 class has methods for querying related rows
27 <sect2 id="zend.db.table.relationships.defining">
29 <title>Defining Relationships</title>
32 Define classes for each of your tables, extending the abstract class
33 <classname>Zend_Db_Table_Abstract</classname>
35 <xref linkend="zend.db.table.defining"/>
38 <xref linkend="zend.db.adapter.example-database"/>
39 for a description of the
40 example database for which the following example code is
46 <acronym>PHP</acronym>
47 class definitions for these tables:
50 <programlisting language="php"><![CDATA[
51 class Accounts extends Zend_Db_Table_Abstract
53 protected $_name = 'accounts';
54 protected $_dependentTables = array('Bugs');
57 class Products extends Zend_Db_Table_Abstract
59 protected $_name = 'products';
60 protected $_dependentTables = array('BugsProducts');
63 class Bugs extends Zend_Db_Table_Abstract
65 protected $_name = 'bugs';
67 protected $_dependentTables = array('BugsProducts');
69 protected $_referenceMap = array(
71 'columns' => 'reported_by',
72 'refTableClass' => 'Accounts',
73 'refColumns' => 'account_name'
76 'columns' => 'assigned_to',
77 'refTableClass' => 'Accounts',
78 'refColumns' => 'account_name'
81 'columns' => array('verified_by'),
82 'refTableClass' => 'Accounts',
83 'refColumns' => array('account_name')
88 class BugsProducts extends Zend_Db_Table_Abstract
90 protected $_name = 'bugs_products';
92 protected $_referenceMap = array(
94 'columns' => array('bug_id'),
95 'refTableClass' => 'Bugs',
96 'refColumns' => array('bug_id')
99 'columns' => array('product_id'),
100 'refTableClass' => 'Products',
101 'refColumns' => array('product_id')
110 <classname>Zend_Db_Table</classname>
111 to emulate cascading UPDATE and DELETE
112 operations, declare the
113 <varname>$_dependentTables</varname>
114 array in the class for the
115 parent table. List the class name for each dependent table.
116 Use the class name, not the
118 <acronym>SQL</acronym>
126 <varname>$_dependentTables</varname>
127 if you use referential
128 integrity constraints in the
129 <acronym>RDBMS</acronym>
130 server to implement cascading
132 <xref linkend="zend.db.table.relationships.cascading"/>
141 <varname>$_referenceMap</varname>
142 array in the class for each dependent
143 table. This is an associative array of reference
144 "rules". A reference rule identifies
145 which table is the parent table in the relationship,
146 and also lists which columns in the
147 dependent table reference which columns in the parent
152 The rule key is a string used as an index to the
153 <varname>$_referenceMap</varname>
154 array. This rule key is used to identify each reference relationship. Choose a
155 descriptive name for this rule key. It's best to use a string that can be part of a
156 <acronym>PHP</acronym>
157 method name, as you will see later.
162 <acronym>PHP</acronym>
163 code above, the rule keys in the Bugs table class
165 <code>'Reporter'</code>
167 <code>'Engineer'</code>
169 <code>'Verifier'</code>
171 <code>'Product'</code>
176 The value of each rule entry in the
177 <varname>$_referenceMap</varname>
179 associative array. The elements of this rule entry are described below:
185 <emphasis>columns</emphasis>
186 => A string or an array of strings
187 naming the foreign key column name(s) in the
192 It's common for this to be a single column, but some tables have multi-column
199 <emphasis>refTableClass</emphasis>
200 => The class name of the parent table. Use
201 the class name, not the physical name
203 <acronym>SQL</acronym>
208 It's common for a dependent table to have only one reference to its parent
209 table, but some tables have multiple references to the same parent table. In
211 example database, there is one reference from the
215 <code>products</code>
216 table, but three references from the
219 <code>accounts</code>
220 table. Put each reference
221 in a separate entry in the
222 <varname>$_referenceMap</varname>
229 <emphasis>refColumns</emphasis>
230 => A string or an array of
231 strings naming the primary key column name(s) in the
236 It's common for this to be a single column, but some tables have multi-column
237 keys. If the reference uses a multi-column key, the order of columns in the
238 <code>'columns'</code>
239 entry must match the order of columns in the
240 <code>'refColumns'</code>
245 It is optional to specify this element. If you don't specify the
246 <code>refColumns</code>
247 , the column(s) reported as the primary key columns of
248 the parent table are used
255 <emphasis>onDelete</emphasis>
256 => The rule for an action to
257 execute if a row is deleted in the parent table. See
258 <xref linkend="zend.db.table.relationships.cascading"/>
259 for more information.
265 <emphasis>onUpdate</emphasis>
266 => The rule for an action to
267 execute if values in primary key columns are updated
268 in the parent table. See
269 <xref linkend="zend.db.table.relationships.cascading"/>
270 for more information.
277 <sect2 id="zend.db.table.relationships.fetching.dependent">
279 <title>Fetching a Dependent Rowset</title>
282 If you have a Row object as the result of a query on a parent table, you can fetch
284 from dependent tables that reference the current row. Use the method:
287 <programlisting language="php"><![CDATA[
288 $row->findDependentRowset($table, [$rule]);
292 This method returns a
293 <classname>Zend_Db_Table_Rowset_Abstract</classname>
295 containing a set of rows from the dependent table
296 <varname>$table</varname>
298 to the row identified by the
299 <varname>$row</varname>
305 <varname>$table</varname>
306 can be a string that specifies the
307 dependent table by its class name. You can also
308 specify the dependent table by using an
309 object of that table class.
312 <example id="zend.db.table.relationships.fetching.dependent.example">
314 <title>Fetching a Dependent Rowset</title>
317 This example shows getting a Row object from the table
318 <code>Accounts</code>
322 reported by that account.
325 <programlisting language="php"><![CDATA[
326 $accountsTable = new Accounts();
327 $accountsRowset = $accountsTable->find(1234);
328 $user1234 = $accountsRowset->current();
330 $bugsReportedByUser = $user1234->findDependentRowset('Bugs');
337 <varname>$rule</varname>
338 is optional. It is a string that names the
340 <varname>$_referenceMap</varname>
341 array of the dependent table class. If
342 you don't specify a rule, the first rule in the
343 array that references the parent table
344 is used. If you need to use a rule other than the
345 first, you need to specify the key.
349 In the example code above, the rule key is not specified, so the rule used by default
351 the first one that matches the parent table. This is the rule
352 <code>'Reporter'</code>
356 <example id="zend.db.table.relationships.fetching.dependent.example-by">
358 <title>Fetching a Dependent Rowset By a Specific Rule</title>
361 This example shows getting a Row object from the table
362 <code>Accounts</code>
366 assigned to be fixed by the user of that account. The
368 corresponds to this reference relationship in this example is
369 <code>'Engineer'</code>
373 <programlisting language="php"><![CDATA[
374 $accountsTable = new Accounts();
375 $accountsRowset = $accountsTable->find(1234);
376 $user1234 = $accountsRowset->current();
378 $bugsAssignedToUser = $user1234->findDependentRowset('Bugs', 'Engineer');
384 You can also add criteria, ordering and limits to your relationships using the parent
390 <example id="zend.db.table.relationships.fetching.dependent.example-by-select">
392 <title>Fetching a Dependent Rowset using a Zend_Db_Table_Select</title>
395 This example shows getting a Row object from the table
396 <code>Accounts</code>
400 assigned to be fixed by the user of that
401 account, limited only to 3 rows and
405 <programlisting language="php"><![CDATA[
406 $accountsTable = new Accounts();
407 $accountsRowset = $accountsTable->find(1234);
408 $user1234 = $accountsRowset->current();
409 $select = $accountsTable->select()->order('name ASC')
412 $bugsAssignedToUser = $user1234->findDependentRowset('Bugs',
419 Alternatively, you can query rows from a dependent table using a special mechanism
420 called a "magic method".
421 <classname>Zend_Db_Table_Row_Abstract</classname>
424 <methodname>findDependentRowset('<TableClass>',
425 '<Rule>')</methodname>
426 if you invoke a method on the Row object matching
427 either of the following patterns:
433 <code>$row->find<TableClass>()</code>
439 <code>$row->find<TableClass>By<Rule>()</code>
445 In the patterns above,
446 <code><TableClass></code>
448 <code><Rule></code>
450 strings that correspond to the class name of the dependent table, and the dependent
451 table's rule key that references the parent table.
457 Some application frameworks, such as Ruby on Rails, use a mechanism called
458 "inflection" to allow the spelling of identifiers to change depending on usage. For
460 <classname>Zend_Db_Table_Row</classname>
461 does not provide any inflection
462 mechanism. The table identity and the rule key named
463 in the method call must match
464 the spelling of the class and rule key exactly.
469 <example id="zend.db.table.relationships.fetching.dependent.example-magic">
471 <title>Fetching Dependent Rowsets using the Magic Method</title>
474 This example shows finding dependent Rowsets equivalent to those in the previous
475 examples. In this case, the application uses the magic method invocation instead of
476 specifying the table and rule as strings.
479 <programlisting language="php"><![CDATA[
480 $accountsTable = new Accounts();
481 $accountsRowset = $accountsTable->find(1234);
482 $user1234 = $accountsRowset->current();
484 // Use the default reference rule
485 $bugsReportedBy = $user1234->findBugs();
487 // Specify the reference rule
488 $bugsAssignedTo = $user1234->findBugsByEngineer();
495 <sect2 id="zend.db.table.relationships.fetching.parent">
497 <title>Fetching a Parent Row</title>
500 If you have a Row object as the result of a query on a dependent table, you can fetch
501 the row in the parent to which the dependent row refers. Use the method:
504 <programlisting language="php"><![CDATA[
505 $row->findParentRow($table, [$rule]);
509 There always should be exactly one row in the parent table referenced by a dependent
510 row, therefore this method returns a Row object, not a Rowset object.
515 <varname>$table</varname>
516 can be a string that specifies the parent
517 table by its class name. You can also specify
518 the parent table by using an object of
522 <example id="zend.db.table.relationships.fetching.parent.example">
524 <title>Fetching the Parent Row</title>
527 This example shows getting a Row object from the table
530 example one of those bugs with status 'NEW'), and finding the row in the
531 <code>Accounts</code>
532 table for the user who reported the bug.
535 <programlisting language="php"><![CDATA[
536 $bugsTable = new Bugs();
537 $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?' => 'NEW'));
538 $bug1 = $bugsRowset->current();
540 $reporter = $bug1->findParentRow('Accounts');
547 <varname>$rule</varname>
548 is optional. It is a string that names the
550 <varname>$_referenceMap</varname>
551 array of the dependent table class. If
552 you don't specify a rule, the first rule in the
553 array that references the parent table
554 is used. If you need to use a rule other than the
555 first, you need to specify the key.
559 In the example above, the rule key is not specified, so the rule used by default is the
560 first one that matches the parent table. This is the rule
561 <code>'Reporter'</code>
565 <example id="zend.db.table.relationships.fetching.parent.example-by">
567 <title>Fetching a Parent Row By a Specific Rule</title>
570 This example shows getting a Row object from the table
573 finding the account for the engineer assigned to fix that bug. The rule key
575 that corresponds to this reference relationship in this example is
576 <code>'Engineer'</code>
580 <programlisting language="php"><![CDATA[
581 $bugsTable = new Bugs();
582 $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
583 $bug1 = $bugsRowset->current();
585 $engineer = $bug1->findParentRow('Accounts', 'Engineer');
591 Alternatively, you can query rows from a parent table using a "magic method".
592 <classname>Zend_Db_Table_Row_Abstract</classname>
594 <methodname>findParentRow('<TableClass>', '<Rule>')</methodname>
596 invoke a method on the Row object matching either of the following patterns:
602 <code>$row->findParent<TableClass>([Zend_Db_Table_Select $select])</code>
608 <code>$row->findParent<TableClass>By<Rule>([Zend_Db_Table_Select
615 In the patterns above,
616 <code><TableClass></code>
618 <code><Rule></code>
619 are strings that correspond to the class name of the parent table, and the dependent
620 table's rule key that references the parent table.
626 The table identity and the rule key named in the method call must match the
627 spelling of the class and rule key exactly.
632 <example id="zend.db.table.relationships.fetching.parent.example-magic">
634 <title>Fetching the Parent Row using the Magic Method</title>
637 This example shows finding parent Rows equivalent to those in the previous
638 examples. In this case, the application uses the magic method invocation instead of
639 specifying the table and rule as strings.
642 <programlisting language="php"><![CDATA[
643 $bugsTable = new Bugs();
644 $bugsRowset = $bugsTable->fetchAll(array('bug_status = ?', 'NEW'));
645 $bug1 = $bugsRowset->current();
647 // Use the default reference rule
648 $reporter = $bug1->findParentAccounts();
650 // Specify the reference rule
651 $engineer = $bug1->findParentAccountsByEngineer();
658 <sect2 id="zend.db.table.relationships.fetching.many-to-many">
660 <title>Fetching a Rowset via a Many-to-many Relationship</title>
663 If you have a Row object as the result of a query on one table in a many-to-many
664 relationship (for purposes of the example, call this the "origin" table), you can
666 corresponding rows in the other table (call this the "destination" table) via an
667 intersection table. Use the method:
670 <programlisting language="php"><![CDATA[
671 $row->findManyToManyRowset($table,
675 [Zend_Db_Table_Select $select]
681 This method returns a
682 <classname>Zend_Db_Table_Rowset_Abstract</classname>
685 <varname>$table</varname>
686 , satisfying the many-to-many relationship.
687 The current Row object
688 <varname>$row</varname>
689 from the origin table is used to find
690 rows in the intersection table, and that is joined
691 to the destination table.
696 <varname>$table</varname>
697 can be a string that specifies the
698 destination table in the many-to-many relationship by
699 its class name. You can also
700 specify the destination table by using an object of that
706 <varname>$intersectionTable</varname>
707 can be a string that specifies
708 the intersection table between the two tables in the
709 many-to-many relationship by
710 its class name. You can also specify the intersection table
711 by using an object of that
715 <example id="zend.db.table.relationships.fetching.many-to-many.example">
717 <title>Fetching a Rowset with the Many-to-many Method</title>
720 This example shows getting a Row object from the origin table
722 , and finding rows from the destination table
723 <code>Products</code>
724 , representing products related to that bug.
727 <programlisting language="php"><![CDATA[
728 $bugsTable = new Bugs();
729 $bugsRowset = $bugsTable->find(1234);
730 $bug1234 = $bugsRowset->current();
732 $productsRowset = $bug1234->findManyToManyRowset('Products',
739 The third and fourth arguments
740 <varname>$rule1</varname>
742 <varname>$rule2</varname>
743 are optional. These are strings that name the rule keys in the
744 <varname>$_referenceMap</varname>
745 array of the intersection table.
750 <varname>$rule1</varname>
751 key names the rule for the relationship from the
752 intersection table to the origin table.
753 In this example, this is the relationship from
754 <code>BugsProducts</code>
762 <varname>$rule2</varname>
763 key names the rule for the relationship from the
764 intersection table to the destination
765 table. In this example, this is the relationship
769 <code>Products</code>
774 Similarly to the methods for finding parent and dependent rows, if you don't specify a
775 rule, the method uses the first rule in the
776 <varname>$_referenceMap</varname>
778 matches the tables in the relationship. If you need to use a rule other than
780 you need to specify the key.
784 In the example code above, the rule key is not specified, so the rules used by default
785 are the first ones that match. In this case,
786 <varname>$rule1</varname>
788 <code>'Reporter'</code>
790 <varname>$rule2</varname>
792 <code>'Product'</code>
796 <example id="zend.db.table.relationships.fetching.many-to-many.example-by">
798 <title>Fetching a Rowset with the Many-to-many Method By a Specific Rule</title>
801 This example shows geting a Row object from the origin table
803 , and finding rows from the destination table
804 <code>Products</code>
805 , representing products related to that bug.
808 <programlisting language="php"><![CDATA[
809 $bugsTable = new Bugs();
810 $bugsRowset = $bugsTable->find(1234);
811 $bug1234 = $bugsRowset->current();
813 $productsRowset = $bug1234->findManyToManyRowset('Products',
821 Alternatively, you can query rows from the destination table in a many-to-many
822 relationship using a "magic method."
823 <classname>Zend_Db_Table_Row_Abstract</classname>
825 <code>findManyToManyRowset('<TableClass>',
826 '<IntersectionTableClass>', '<Rule1>', '<Rule2>')</code>
828 a method matching any of the following patterns:
834 <code>$row->find<TableClass>Via<IntersectionTableClass>
835 ([Zend_Db_Table_Select $select])</code>
841 <code>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>
842 ([Zend_Db_Table_Select $select])</code>
848 <code>$row->find<TableClass>Via<IntersectionTableClass>By<Rule1>And<Rule2>
849 ([Zend_Db_Table_Select $select])</code>
855 In the patterns above,
856 <code><TableClass></code>
858 <code><IntersectionTableClass></code>
859 are strings that correspond to the class
860 names of the destination table and the
861 intersection table, respectively.
862 <code><Rule1></code>
864 <code><Rule2></code>
865 are strings that correspond
866 to the rule keys in the intersection table that reference the
868 destination table, respectively.
874 The table identities and the rule keys named in the method call must match the
875 spelling of the class and rule key exactly.
880 <example id="zend.db.table.relationships.fetching.many-to-many.example-magic">
882 <title>Fetching Rowsets using the Magic Many-to-many Method</title>
885 This example shows finding rows in the destination table of a many-to-many
886 relationship representing products related to a given bug.
889 <programlisting language="php"><![CDATA[
890 $bugsTable = new Bugs();
891 $bugsRowset = $bugsTable->find(1234);
892 $bug1234 = $bugsRowset->current();
894 // Use the default reference rule
895 $products = $bug1234->findProductsViaBugsProducts();
897 // Specify the reference rule
898 $products = $bug1234->findProductsViaBugsProductsByBug();
905 <sect2 id="zend.db.table.relationships.cascading">
907 <title>Cascading Write Operations</title>
911 <title>Declare DRI in the database:</title>
914 Declaring cascading operations in
915 <classname>Zend_Db_Table</classname>
917 <emphasis>only</emphasis>
919 <acronym>RDBMS</acronym>
920 brands that do not support
921 declarative referential integrity (DRI).
925 For example, if you use MySQL's MyISAM storage engine, or SQLite, these solutions
927 not support DRI. You may find it helpful to declare the cascading operations
929 <classname>Zend_Db_Table</classname>
935 <acronym>RDBMS</acronym>
936 implements DRI and the
937 <code>ON DELETE</code>
939 <code>ON UPDATE</code>
940 clauses, you should declare these clauses in your database
941 schema, instead of using
942 the cascading feature in
943 <classname>Zend_Db_Table</classname>
944 . Declaring cascading DRI rules in the
945 <acronym>RDBMS</acronym>
946 is better for database performance, consistency, and
951 Most importantly, do not declare cascading operations both in the
952 <acronym>RDBMS</acronym>
954 <classname>Zend_Db_Table</classname>
961 You can declare cascading operations to execute against a dependent table when you
964 <constant>UPDATE</constant>
966 <constant>DELETE</constant>
971 <example id="zend.db.table.relationships.cascading.example-delete">
973 <title>Example of a Cascading Delete</title>
976 This example shows deleting a row in the
977 <code>Products</code>
979 configured to automatically delete dependent rows in the
984 <programlisting language="php"><![CDATA[
985 $productsTable = new Products();
986 $productsRowset = $productsTable->find(1234);
987 $product1234 = $productsRowset->current();
989 $product1234->delete();
990 // Automatically cascades to Bugs table
991 // and deletes dependent rows.
997 Similarly, if you use
998 <constant>UPDATE</constant>
999 to change the value of a primary key
1000 in a parent table, you may want the value in foreign
1001 keys of dependent tables to be
1002 updated automatically to match the new value, so that such
1003 references are kept up to
1008 It's usually not necessary to update the value of a primary key that was generated by a
1009 sequence or other mechanism. But if you use a
1010 <emphasis>natural key</emphasis>
1012 change value occasionally, it is more likely that you need to apply cascading
1014 to dependent tables.
1018 To declare a cascading relationship in the
1019 <classname>Zend_Db_Table</classname>
1022 <varname>$_referenceMap</varname>
1023 . Set the associative array keys
1024 <code>'onDelete'</code>
1026 <code>'onUpdate'</code>
1027 to the string 'cascade' (or the
1029 <constant>self::CASCADE</constant>
1030 ). Before a row is deleted from the parent
1031 table, or its primary key values updated, any
1032 rows in the dependent table that refer to
1033 the parent's row are deleted or updated first.
1036 <example id="zend.db.table.relationships.cascading.example-declaration">
1038 <title>Example Declaration of Cascading Operations</title>
1041 In the example below, rows in the
1043 table are automatically deleted
1045 <code>Products</code>
1046 table to which they refer is deleted. The
1047 <code>'onDelete'</code>
1048 element of the reference map entry is set to
1049 <constant>self::CASCADE</constant>
1054 No cascading update is done in the example below if the primary key value in the
1055 parent class is changed. The
1056 <code>'onUpdate'</code>
1057 element of the reference map
1059 <constant>self::RESTRICT</constant>
1060 . You can get the same result by
1062 <code>'onUpdate'</code>
1066 <programlisting language="php"><![CDATA[
1067 class BugsProducts extends Zend_Db_Table_Abstract
1070 protected $_referenceMap = array(
1072 'columns' => array('product_id'),
1073 'refTableClass' => 'Products',
1074 'refColumns' => array('product_id'),
1075 'onDelete' => self::CASCADE,
1076 'onUpdate' => self::RESTRICT
1081 ]]></programlisting>
1085 <sect3 id="zend.db.table.relationships.cascading.notes">
1087 <title>Notes Regarding Cascading Operations</title>
1091 Cascading operations invoked by
1092 <classname>Zend_Db_Table</classname>
1099 This means that if your database implements and enforces referential integrity
1100 constraints, a cascading
1101 <constant>UPDATE</constant>
1103 <classname>Zend_Db_Table</classname>
1104 class conflicts with the constraint, and
1105 results in a referential integrity
1106 violation. You can use cascading
1107 <constant>UPDATE</constant>
1109 <classname>Zend_Db_Table</classname>
1110 <emphasis>only</emphasis>
1111 if your database does not enforce that referential
1112 integrity constraint.
1117 <constant>DELETE</constant>
1118 suffers less from the problem of referential
1119 integrity violations. You can delete
1120 dependent rows as a non-atomic action before
1121 deleting the parent row that they
1127 <constant>UPDATE</constant>
1129 <constant>DELETE</constant>
1131 changing the database in a non-atomic way also creates the risk that another
1132 database user can see the data in an inconsistent state. For example, if you delete
1133 a row and all its dependent rows, there is a small chance that another database
1134 client program can query the database after you have deleted the dependent rows, but
1135 before you delete the parent row. That client program may see the parent row with no
1136 dependent rows, and assume this is the intended state of the data. There is no way
1137 for that client to know that its query read the database in the middle of a change.
1141 The issue of non-atomic change can be mitigated by using transactions to isolate
1142 your change. But some
1143 <acronym>RDBMS</acronym>
1144 brands don't support transactions, or
1145 allow clients to read "dirty" changes that have
1146 not been committed yet.
1151 Cascading operations in
1152 <classname>Zend_Db_Table</classname>
1155 <classname>Zend_Db_Table</classname>
1161 Cascading deletes and updates defined in your
1162 <classname>Zend_Db_Table</classname>
1163 classes are applied if you execute the
1164 <methodname>save()</methodname>
1166 <methodname>delete()</methodname>
1167 methods on the Row class. However, if you update
1168 or delete data using another
1169 interface, such as a query tool or another application,
1170 the cascading operations are
1171 not applied. Even when using
1172 <methodname>update()</methodname>
1174 <methodname>delete()</methodname>
1177 <classname>Zend_Db_Adapter</classname>
1178 class, cascading operations defined in
1180 <classname>Zend_Db_Table</classname>
1181 classes are not executed.
1187 <constant>INSERT</constant>
1193 There is no support for a cascading
1194 <constant>INSERT</constant>
1196 row to a parent table in one operation, and insert row(s) to a
1197 dependent table in a
1207 vim:se ts=4 sw=4 et: