|
eZ Publish
[4.0]
|
00001 <?php 00002 // 00003 // Definition of eZPersistentObject class 00004 // 00005 // Created on: <16-Apr-2002 11:08:14 amos> 00006 // 00007 // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00008 // SOFTWARE NAME: eZ Publish 00009 // SOFTWARE RELEASE: 4.0.x 00010 // COPYRIGHT NOTICE: Copyright (C) 1999-2008 eZ Systems AS 00011 // SOFTWARE LICENSE: GNU General Public License v2.0 00012 // NOTICE: > 00013 // This program is free software; you can redistribute it and/or 00014 // modify it under the terms of version 2.0 of the GNU General 00015 // Public License as published by the Free Software Foundation. 00016 // 00017 // This program is distributed in the hope that it will be useful, 00018 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00019 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00020 // GNU General Public License for more details. 00021 // 00022 // You should have received a copy of version 2.0 of the GNU General 00023 // Public License along with this program; if not, write to the Free 00024 // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 00025 // MA 02110-1301, USA. 00026 // 00027 // 00028 // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ## 00029 // 00030 00031 /*! 00032 \defgroup eZKernel Kernel system 00033 */ 00034 00035 /*! 00036 \class eZPersistentObject ezpersistentobject.php 00037 \ingroup eZKernel 00038 \brief Allows for object persistence in a database 00039 00040 Classes which stores simple types in databases should inherit from this 00041 and implement the definition() function. The class will then get initialization, 00042 fetching, listing, moving, storing and deleting for free as well as attribute 00043 access. The new class must have a constructor which takes one parameter called 00044 \c $row and pass that this constructor. 00045 00046 \code 00047 class MyClass extends eZPersistentObject 00048 { 00049 function MyClass( $row ) 00050 { 00051 $this->eZPersistentObject( $row ); 00052 } 00053 } 00054 \endcode 00055 00056 */ 00057 00058 //include_once( "lib/ezdb/classes/ezdb.php" ); 00059 require_once( "lib/ezutils/classes/ezdebug.php" ); 00060 00061 class eZPersistentObject 00062 { 00063 /*! 00064 Initializes the object with the row \a $row. It will try to set 00065 each field taken from the database row. Calls fill to do the job. 00066 If the parameter \a $row is an integer it will try to fetch it 00067 from the database using it as the unique ID. 00068 */ 00069 function eZPersistentObject( $row ) 00070 { 00071 $this->PersistentDataDirty = false; 00072 if ( is_numeric( $row ) ) 00073 $row = $this->fetch( $row, false ); 00074 $this->fill( $row ); 00075 } 00076 00077 /*! 00078 Tries to fill in the data in the object by using the object definition 00079 which is returned by the function definition() and the database row 00080 data \a $row. Each field will be fetch from the definition and then 00081 use that fieldname to fetch from the row and set the data. 00082 */ 00083 function fill( $row ) 00084 { 00085 if ( !is_array( $row ) ) 00086 return; 00087 $def = $this->definition(); 00088 $fields = $def["fields"]; 00089 $intersectList = array_intersect_key( $fields, 00090 $row ); 00091 00092 foreach ( $intersectList as $key => $item ) 00093 { 00094 if ( is_array( $item ) ) 00095 { 00096 $item = $item['name']; 00097 } 00098 $this->$item = $row[$key]; 00099 } 00100 00101 foreach( array_diff_key( $fields, $intersectList ) as $item ) 00102 { 00103 if ( is_array( $item ) ) 00104 { 00105 $item = $item['name']; 00106 } 00107 $this->$item = null; 00108 } 00109 } 00110 00111 /*! 00112 \private 00113 \static 00114 For the given array \a fields treats its keys (for associative array) or 00115 values (for non-associative array) as table fields names and replaces them 00116 with short names (aliases) found in \a fieldDefs. 00117 */ 00118 static function replaceFieldsWithShortNames( $db, $fieldDefs, &$fields ) 00119 { 00120 if ( !$db->useShortNames() || !$fields ) 00121 return; 00122 00123 $short_fields_names = array(); 00124 foreach ( $fields as $key => $val ) 00125 { 00126 if( is_numeric( $key ) ) // $fields is not an associative array 00127 { 00128 if ( array_key_exists( $val, $fieldDefs ) && 00129 array_key_exists( 'short_name', $fieldDefs[$val] ) ) 00130 { 00131 $short_fields_names[$key] = $fieldDefs[$val]['short_name']; 00132 } 00133 else 00134 $short_fields_names[$key] = $val; 00135 } 00136 else // $fields is an associative array 00137 { 00138 if ( array_key_exists( $key, $fieldDefs ) && 00139 array_key_exists( 'short_name', $fieldDefs[$key] ) ) 00140 { 00141 $newkey = $fieldDefs[$key]['short_name']; 00142 } 00143 else 00144 $newkey = $key; 00145 $short_fields_names[$newkey] = $val; 00146 } 00147 00148 } 00149 $fields = $short_fields_names; 00150 } 00151 00152 /*! 00153 Fetches the number of rows by using the object definition. 00154 Uses fetchObjectList for the actual SQL handling. 00155 00156 See fetchObjectList() for a full description of the input parameters. 00157 */ 00158 static function count( $def, $conds = null, $field = null ) 00159 { 00160 if ( !isset( $field ) ) 00161 { 00162 $field = '*'; 00163 } 00164 $customFields = array( array( 'operation' => 'COUNT( ' . $field . ' )', 'name' => 'row_count' ) ); 00165 $rows = eZPersistentObject::fetchObjectList( $def, array(), $conds, array(), null, false, false, $customFields ); 00166 return $rows[0]['row_count']; 00167 } 00168 00169 /*! 00170 Creates an SQL query out of the different parameters and returns an object with the result. 00171 If \a $asObject is true the returned item is an object otherwise a db row. 00172 Uses fetchObjectList for the actual SQL handling and just returns the first row item. 00173 00174 See fetchObjectList() for a full description of the input parameters. 00175 */ 00176 static function fetchObject( /*! The definition structure */ 00177 $def, 00178 /*! If defined determines the fields which are extracted, if not all fields are fetched */ 00179 $field_filters, 00180 /*! An array of conditions which determines which rows are fetched*/ 00181 $conds, 00182 $asObject = true, 00183 /*! An array of elements to group by */ 00184 $grouping = null, 00185 /*! An array of extra fields to fetch, each field may be a SQL operation */ 00186 $custom_fields = null ) 00187 { 00188 $rows = eZPersistentObject::fetchObjectList( $def, $field_filters, $conds, 00189 array(), null, $asObject, 00190 $grouping, $custom_fields ); 00191 if ( $rows ) 00192 return $rows[0]; 00193 return null; 00194 } 00195 00196 /*! 00197 Removes the object from the database, it will use the keys in the object 00198 definition to figure out which table row should be removed unless \a $conditions 00199 is defined as an array with fieldnames. 00200 It uses removeObject to do the real job and passes the object defintion, 00201 conditions and extra conditions \a $extraConditions to this function. 00202 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00203 the calls within a db transaction; thus within db->begin and db->commit. 00204 */ 00205 function remove( $conditions = null, $extraConditions = null ) 00206 { 00207 $def = $this->definition(); 00208 $keys = $def["keys"]; 00209 if ( !is_array( $conditions ) ) 00210 { 00211 $conditions = array(); 00212 foreach ( $keys as $key ) 00213 { 00214 $value = $this->attribute( $key ); 00215 $conditions[$key] = $value; 00216 } 00217 } 00218 eZPersistentObject::removeObject( $def, $conditions, $extraConditions ); 00219 } 00220 00221 /*! 00222 Deletes the object from the table defined in \a $def with conditions \a $conditions 00223 and extra conditions \a $extraConditions. The extra conditions will either be 00224 appended to the existing conditions or overwrite existing fields. 00225 Uses conditionText() to create the condition SQL. 00226 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00227 the calls within a db transaction; thus within db->begin and db->commit. 00228 */ 00229 static function removeObject( $def, $conditions = null, $extraConditions = null ) 00230 { 00231 $db = eZDB::instance(); 00232 00233 $table = $def["name"]; 00234 if ( is_array( $extraConditions ) ) 00235 { 00236 foreach ( $extraConditions as $key => $cond ) 00237 { 00238 $conditions[$key] = $cond; 00239 } 00240 } 00241 00242 /* substitute fields mentioned the conditions whith their 00243 short names (if any) 00244 */ 00245 $fields = $def['fields']; 00246 eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conditions ); 00247 00248 $cond_text = eZPersistentObject::conditionText( $conditions ); 00249 00250 $db->query( "DELETE FROM $table $cond_text" ); 00251 } 00252 00253 /*! 00254 Stores the object in the database, uses storeObject() to do the actual 00255 job and passes \a $fieldFilters to it. 00256 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00257 the calls within a db transaction; thus within db->begin and db->commit. 00258 */ 00259 function store( $fieldFilters = null ) 00260 { 00261 eZPersistentObject::storeObject( $this, $fieldFilters ); 00262 } 00263 00264 /*! 00265 Makes sure data is stored if the data is considered dirty. 00266 \sa hasDirtyData 00267 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00268 the calls within a db transaction; thus within db->begin and db->commit. 00269 */ 00270 function sync( $fieldFilters = null ) 00271 { 00272 if ( $this->hasDirtyData() ) 00273 $this->store( $fieldFilters ); 00274 } 00275 00276 /*! 00277 \private 00278 Stores the data in \a $obj to database. 00279 \param fieldFilters If specified only certain fields will be stored. 00280 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00281 the calls within a db transaction; thus within db->begin and db->commit. 00282 */ 00283 static function storeObject( $obj, $fieldFilters = null ) 00284 { 00285 $db = eZDB::instance(); 00286 $useFieldFilters = ( isset( $fieldFilters ) && is_array( $fieldFilters ) && $fieldFilters ); 00287 00288 $def = $obj->definition(); 00289 $fields = $def["fields"]; 00290 $keys = $def["keys"]; 00291 $table = $def["name"]; 00292 $relations = isset( $def["relations"] ) ? $def["relations"] : null; 00293 $insert_object = false; 00294 $exclude_fields = array(); 00295 foreach ( $keys as $key ) 00296 { 00297 $value = $obj->attribute( $key ); 00298 if ( is_null( $value ) ) 00299 { 00300 $insert_object = true; 00301 $exclude_fields[] = $key; 00302 } 00303 } 00304 00305 if ( $useFieldFilters ) 00306 $insert_object = false; 00307 00308 $use_fields = array_diff( array_keys( $fields ), $exclude_fields ); 00309 // If we filter out some of the fields we need to intersect it with $use_fields 00310 if ( is_array( $fieldFilters ) ) 00311 $use_fields = array_intersect( $use_fields, $fieldFilters ); 00312 $doNotEscapeFields = array(); 00313 $changedValueFields = array(); 00314 $numericDataTypes = array( 'integer', 'float', 'double' ); 00315 00316 foreach ( $use_fields as $field_name ) 00317 { 00318 $field_def = $fields[$field_name]; 00319 $value = $obj->attribute( $field_name ); 00320 00321 if ( is_null( $value ) ) 00322 { 00323 if ( ! is_array( $field_def ) ) 00324 { 00325 $exclude_fields[] = $field_name; 00326 } 00327 else 00328 { 00329 if ( array_key_exists( 'default', $field_def ) && 00330 (! is_null( $field_def['default'] ) || 00331 ( $field_name == 'data_int' && 00332 array_key_exists( 'required', $field_def ) && 00333 $field_def[ 'required' ] == false ) ) ) 00334 { 00335 $obj->setAttribute( $field_name, $field_def[ 'default' ] ); 00336 } 00337 else 00338 { 00339 //if ( in_array( $field_def['datatype'], $numericDataTypes ) 00340 $exclude_fields[] = $field_name; 00341 } 00342 } 00343 } 00344 00345 if ( strlen( $value ) == 0 && 00346 is_array( $field_def ) && 00347 in_array( $field_def['datatype'], $numericDataTypes ) && 00348 array_key_exists( 'default', $field_def ) && 00349 ( is_null( $field_def[ 'default' ] ) || is_numeric( $field_def[ 'default' ] ) ) ) 00350 { 00351 $obj->setAttribute( $field_name, $field_def[ 'default' ] ); 00352 } 00353 00354 if ( !is_null( $value ) && 00355 $field_def['datatype'] === 'string' && 00356 array_key_exists( 'max_length', $field_def ) && 00357 $field_def['max_length'] > 0 && 00358 strlen( $value ) > $field_def['max_length'] ) 00359 { 00360 $obj->setAttribute( $field_name, substr( $value, 0, $field_def['max_length'] ) ); 00361 eZDebug::writeDebug( $value, "truncation of $field_name to max_length=". $field_def['max_length'] ); 00362 } 00363 $bindDataTypes = array( 'text' ); 00364 if ( $db->bindingType() != eZDBInterface::BINDING_NO && 00365 strlen( $value ) > 2000 && 00366 is_array( $field_def ) && 00367 in_array( $field_def['datatype'], $bindDataTypes ) 00368 ) 00369 { 00370 $boundValue = $db->bindVariable( $value, $field_def ); 00371 // $obj->setAttribute( $field_name, $value ); 00372 $doNotEscapeFields[] = $field_name; 00373 $changedValueFields[$field_name] = $boundValue; 00374 } 00375 00376 } 00377 $key_conds = array(); 00378 foreach ( $keys as $key ) 00379 { 00380 $value = $obj->attribute( $key ); 00381 $key_conds[$key] = $value; 00382 } 00383 unset( $value ); 00384 00385 $important_keys = $keys; 00386 if ( is_array( $relations ) ) 00387 { 00388 // $important_keys = array(); 00389 foreach( $relations as $relation => $relation_data ) 00390 { 00391 if ( !in_array( $relation, $keys ) ) 00392 $important_keys[] = $relation; 00393 } 00394 } 00395 if ( count( $important_keys ) == 0 && !$useFieldFilters ) 00396 { 00397 $insert_object = true; 00398 } 00399 else if ( !$insert_object ) 00400 { 00401 $rows = eZPersistentObject::fetchObjectList( $def, $keys, $key_conds, 00402 array(), null, false, 00403 null, null ); 00404 if ( count( $rows ) == 0 ) 00405 { 00406 /* If we only want to update some fields in a record 00407 * and that records does not exist, then we should do nothing, only return. 00408 */ 00409 if ( $useFieldFilters ) 00410 return; 00411 00412 $insert_object = true; 00413 } 00414 } 00415 00416 if ( $insert_object ) 00417 { 00418 // Note: When inserting we cannot hone the $fieldFilters parameters 00419 00420 $use_fields = array_diff( array_keys( $fields ), $exclude_fields ); 00421 $use_field_names = $use_fields; 00422 if ( $db->useShortNames() ) 00423 { 00424 $use_short_field_names = $use_field_names; 00425 eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $use_short_field_names ); 00426 $field_text = implode( ', ', $use_short_field_names ); 00427 unset( $use_short_field_names ); 00428 } 00429 else 00430 $field_text = implode( ', ', $use_field_names ); 00431 00432 $use_values_hash = array(); 00433 $escapeFields = array_diff( $use_fields, $doNotEscapeFields ); 00434 00435 foreach ( $escapeFields as $key ) 00436 { 00437 $value = $obj->attribute( $key ); 00438 $field_def = $fields[$key]; 00439 00440 if ( $field_def['datatype'] == 'float' || $field_def['datatype'] == 'double' ) 00441 { 00442 if ( is_null( $value ) ) 00443 { 00444 $use_values_hash[$key] = 'NULL'; 00445 } 00446 else 00447 { 00448 $use_values_hash[$key] = sprintf( '%F', $value ); 00449 } 00450 } 00451 else if ( $field_def['datatype'] == 'int' || $field_def['datatype'] == 'integer' ) 00452 { 00453 if ( is_null( $value ) ) 00454 { 00455 $use_values_hash[$key] = 'NULL'; 00456 } 00457 else 00458 { 00459 $use_values_hash[$key] = sprintf( '%d', $value ); 00460 } 00461 } 00462 else 00463 { 00464 // Note: for more colherence, we might use NULL for sql strings if the php value is NULL and not an empty sring 00465 // but to keep compatibility with ez db, where most string columns are "not null default ''", 00466 // and code feeding us a php null value without meaning it, we do not. 00467 $use_values_hash[$key] = "'" . $db->escapeString( $value ) . "'"; 00468 } 00469 } 00470 foreach ( $doNotEscapeFields as $key ) 00471 { 00472 $use_values_hash[$key] = $changedValueFields[$key]; 00473 } 00474 $use_values = array(); 00475 foreach ( $use_field_names as $field ) 00476 $use_values[] = $use_values_hash[$field]; 00477 unset( $use_values_hash ); 00478 $value_text = implode( ", ", $use_values ); 00479 00480 $sql = "INSERT INTO $table ($field_text) VALUES($value_text)"; 00481 $db->query( $sql ); 00482 00483 if ( isset( $def["increment_key"] ) && 00484 is_string( $def["increment_key"] ) && 00485 !( $obj->attribute( $def["increment_key"] ) > 0 ) ) 00486 { 00487 $inc = $def["increment_key"]; 00488 $id = $db->lastSerialID( $table, $inc ); 00489 if ( $id !== false ) 00490 $obj->setAttribute( $inc, $id ); 00491 } 00492 } 00493 else 00494 { 00495 $use_fields = array_diff( array_keys( $fields ), array_merge( $keys, $exclude_fields ) ); 00496 if ( count( $use_fields ) > 0 ) 00497 { 00498 // If we filter out some of the fields we need to intersect it with $use_fields 00499 if ( is_array( $fieldFilters ) ) 00500 $use_fields = array_intersect( $use_fields, $fieldFilters ); 00501 $use_field_names = array(); 00502 foreach ( $use_fields as $key ) 00503 { 00504 if ( $db->useShortNames() && is_array( $fields[$key] ) && array_key_exists( 'short_name', $fields[$key] ) && strlen( $fields[$key]['short_name'] ) > 0 ) 00505 $use_field_names[$key] = $fields[$key]['short_name']; 00506 else 00507 $use_field_names[$key] = $key; 00508 } 00509 00510 $field_text = ""; 00511 $field_text_len = 0; 00512 $i = 0; 00513 00514 00515 foreach ( $use_fields as $key ) 00516 { 00517 $value = $obj->attribute( $key ); 00518 00519 if ( $fields[$key]['datatype'] == 'float' || $fields[$key]['datatype'] == 'double' ) 00520 { 00521 if (is_null($value)) 00522 $field_text_entry = $use_field_names[$key] . '=NULL'; 00523 else 00524 $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%F', $value ); 00525 } 00526 else if ($fields[$key]['datatype'] == 'int' || $fields[$key]['datatype'] == 'integer' ) 00527 { 00528 if (is_null($value)) 00529 $field_text_entry = $use_field_names[$key] . '=NULL'; 00530 else 00531 $field_text_entry = $use_field_names[$key] . "=" . sprintf( '%d', $value ); 00532 } 00533 else if ( in_array( $use_field_names[$key], $doNotEscapeFields ) ) 00534 { 00535 $field_text_entry = $use_field_names[$key] . "=" . $changedValueFields[$key]; 00536 } 00537 else 00538 { 00539 $field_text_entry = $use_field_names[$key] . "='" . $db->escapeString( $value ) . "'"; 00540 } 00541 00542 $field_text_len += strlen( $field_text_entry ); 00543 $needNewline = false; 00544 if ( $field_text_len > 60 ) 00545 { 00546 $needNewline = true; 00547 $field_text_len = 0; 00548 } 00549 if ( $i > 0 ) 00550 $field_text .= "," . ($needNewline ? "\n " : ' '); 00551 $field_text .= $field_text_entry; 00552 ++$i; 00553 } 00554 $cond_text = eZPersistentObject::conditionText( $key_conds ); 00555 $sql = "UPDATE $table\nSET $field_text$cond_text"; 00556 $db->query( $sql ); 00557 } 00558 } 00559 $obj->setHasDirtyData( false ); 00560 } 00561 00562 /*! 00563 Calls conditionTextByRow with an empty row and \a $conditions. 00564 */ 00565 static function conditionText( $conditions ) 00566 { 00567 return eZPersistentObject::conditionTextByRow( $conditions, null ); 00568 } 00569 00570 /*! 00571 Generates an SQL sentence from the conditions \a $conditions and row data \a $row. 00572 If \a $row is empty (null) it uses the condition data instead of row data. 00573 */ 00574 static function conditionTextByRow( $conditions, $row ) 00575 { 00576 $db = eZDB::instance(); 00577 00578 $where_text = ""; 00579 if ( is_array( $conditions ) and 00580 count( $conditions ) > 0 ) 00581 { 00582 $where_text = "\nWHERE "; 00583 $i = 0; 00584 foreach ( $conditions as $id => $cond ) 00585 { 00586 if ( $i > 0 ) 00587 $where_text .= " AND "; 00588 if ( is_array( $row ) ) 00589 { 00590 $where_text .= $cond . "='" . $db->escapeString( $row[$cond] ) . "'"; 00591 } 00592 else 00593 { 00594 if ( is_array( $cond ) ) 00595 { 00596 if ( is_array( $cond[0] ) ) 00597 { 00598 $where_text .= $id . ' IN ( '; 00599 $j = 0; 00600 foreach ( $cond[0] as $value ) 00601 { 00602 if ( $j > 0 ) 00603 $where_text .= ", "; 00604 $where_text .= "'" . $db->escapeString( $value ) . "'"; 00605 ++$j; 00606 } 00607 $where_text .= ' ) '; 00608 } 00609 else if ( is_array( $cond[1] ) ) 00610 { 00611 $range = $cond[1]; 00612 $where_text .= "$id BETWEEN '" . $db->escapeString( $range[0] ) . "' AND '" . $db->escapeString( $range[1] ) . "'"; 00613 } 00614 else 00615 { 00616 switch ( $cond[0] ) 00617 { 00618 case '>=': 00619 case '<=': 00620 case '<': 00621 case '>': 00622 case '=': 00623 case '<>': 00624 case '!=': 00625 case 'like': 00626 { 00627 $where_text .= $db->escapeString( $id ) . " " . $cond[0] . " '" . $db->escapeString( $cond[1] ) . "'"; 00628 } break; 00629 default: 00630 { 00631 eZDebug::writeError( "Conditional operator '$cond[0]' is not supported.",'eZPersistentObject::conditionTextByRow()' ); 00632 } break; 00633 } 00634 00635 } 00636 } 00637 else 00638 $where_text .= $db->escapeString( $id ) . "='" . $db->escapeString( $cond ) . "'"; 00639 } 00640 ++$i; 00641 } 00642 } 00643 return $where_text; 00644 } 00645 00646 00647 /*! 00648 Creates an SQL query out of the different parameters and returns an array with the result. 00649 If \a $asObject is true the array contains objects otherwise a db row. 00650 00651 00652 \param $def A definition array of all fields, table name and sorting 00653 \param $field_filters If defined determines the fields which are extracted (array of field names), if not all fields are fetched 00654 \param $conds \c null for no special condition or an associative array of fields to filter on. 00655 Syntax is \c FIELD => \c CONDITION, \c CONDITION can be one of: 00656 - Scalar value - Creates a condition where \c FIELD must match the value, e.g 00657 \code array( 'id' => 5 ) \endcode 00658 generates SQL 00659 \code id = 5 \endcode 00660 - Array with two scalar values - Element \c 0 is the match operator and element \c 1 is the scalar value 00661 \code array( 'priority' => array( '>', 5 ) ) \endcode 00662 generates SQL 00663 \code priority > 5 \endcode 00664 - Array with range - Element \c 1 is an array with start and stop of range in array 00665 \code array( 'type' => array( false, array( 1, 5 ) ) ) \endcode 00666 generates SQL 00667 \code type BETWEEN 1 AND 5 \endcode 00668 - Array with multiple elements - Element \c 0 is an array with scalar values 00669 \code array( 'id' => array( array( 1, 5, 7 ) ) ) \endcode 00670 generates SQL 00671 \code id IN ( 1, 5, 7 ) \endcode 00672 \param $sorts An associative array of sorting conditions, if set to \c false ignores settings in \a $def, 00673 if set to \c null uses settingss in \a $def. 00674 Syntax is \c FIELD => \c DIRECTION. \c DIRECTION must either be string \c 'asc' 00675 for ascending or \c 'desc' for descending. 00676 \param $limit An associative array with limitiations, can contain 00677 - offset - Numerical value defining the start offset for the fetch 00678 - length - Numerical value defining the max number of items to return 00679 \param $asObject If \c true then it will return an array with objects, objects are created from class defined in \a $def. 00680 If \c false it will just return the rows fetch from database. 00681 \param $grouping An array of fields to group by or \c null to use grouping in defintion \a $def. 00682 \param $custom_fields Array of \c FIELD elements to add to SQL, can be used to perform custom fetches, e.g counts. 00683 \c FIELD is an associative array containing: 00684 - operation - A text field which is included in the field list 00685 - name - If present it adds 'AS name' to the operation. 00686 \param $custom_tables Array of additional tables. 00687 \param $custom_conds String with sql conditions for 'WHERE' clause. 00688 00689 A full example: 00690 \code 00691 $filter = array( 'id', 'name' ); 00692 $conds = array( 'type' => 5, 00693 'size' => array( false, array( 200, 500 ) ) ); 00694 $sorts = array( 'name' => 'asc' ); 00695 $limit = array( 'offset' => 50, 'length' => 10 ); 00696 eZPersistentObject::fetchObjectList( $def, $filter, $conds, $sorts, $limit, true, false, null ) 00697 \endcode 00698 00699 Counting number of elements. 00700 \code 00701 $custom = array( array( 'operation' => 'count( id )', 00702 'name' => 'count' ) ); 00703 // Here $field_filters is set to an empty array, that way only count is used in fields 00704 $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, false, $custom ); 00705 return $rows[0]['count']; 00706 \endcode 00707 00708 Counting elements per type using grouping 00709 \code 00710 $custom = array( array( 'operation' => 'count( id )', 00711 'name' => 'count' ) ); 00712 $group = array( 'type' ); 00713 $rows = eZPersistentObject::fetchObjectList( $def, array(), null, null, null, false, $group, $custom ); 00714 return $rows[0]['count']; 00715 \endcode 00716 00717 Example to fetch a result with custom conditions. The following example will fetch the attributes to 00718 the contentobject with id 1 and add the contentobject.name in each attribute row with the array key 00719 contentobject_name. 00720 \code 00721 $objectDef = eZContentObject::definition(); 00722 $objectAttributeDef = eZContentObjectAttribute::definition(); 00723 00724 $fields = array(); 00725 $conds = array( $objectDef['name'] . '.id' => 1 ); 00726 $sorts = array( $objectAttributeDef['name'] . '.sort_key_string' => 'asc' ); 00727 00728 $limit = null; 00729 $asObject = false; 00730 $group = false; 00731 00732 $customFields = array( $objectAttributeDef['name'] . '.*', 00733 array( 'operation' => $objectDef['name'] . '.name', 00734 'name' => 'contentobject_name' ) ); 00735 00736 $customTables = array( $objectDef['name'] ); 00737 00738 $languageCode = 'eng-GB'; 00739 $customConds = ' AND ' . $objectDef['name'] . '.current_version=' . $objectAttributeDef['name'] . '.version' . 00740 ' AND ' . $objectDef['name'] . '.id=' . $objectAttributeDef['name'] . '.contentobject_id' . 00741 ' AND ' . $objectAttributeDef['name'] . '.language_code=\'' . $languageCode . '\''; 00742 00743 $rows = eZPersistentObject::fetchObjectList( $objectAttributeDef, $fields, $conds, $sorts, $limit, $asObject, 00744 $group, $customFields, $customTables, $customConds ); 00745 \endcode 00746 */ 00747 static function fetchObjectList( $def, 00748 $field_filters = null, 00749 $conds = null, 00750 $sorts = null, 00751 $limit = null, 00752 $asObject = true, 00753 $grouping = false, 00754 $custom_fields = null, 00755 $custom_tables = null, 00756 $custom_conds = null ) 00757 { 00758 $db = eZDB::instance(); 00759 $fields = $def["fields"]; 00760 $tables = $def["name"]; 00761 $class_name = $def["class_name"]; 00762 if ( is_array( $custom_tables ) ) 00763 { 00764 foreach( $custom_tables as $custom_table ) 00765 $tables .= ', ' . $db->escapeString( $custom_table ); 00766 } 00767 eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $conds ); 00768 if ( is_array( $field_filters ) ) 00769 $field_array = array_unique( array_intersect( 00770 $field_filters, array_keys( $fields ) ) ); 00771 else 00772 $field_array = array_keys( $fields ); 00773 if ( $custom_fields !== null and is_array( $custom_fields ) ) 00774 { 00775 foreach( $custom_fields as $custom_field ) 00776 { 00777 if ( is_array( $custom_field ) ) 00778 { 00779 $custom_text = $custom_field["operation"]; 00780 if ( isset( $custom_field["name"] ) ) 00781 { 00782 $field_name = $custom_field["name"]; 00783 $custom_text .= " AS $field_name"; 00784 } 00785 } 00786 else 00787 { 00788 $custom_text = $custom_field; 00789 } 00790 $field_array[] = $custom_text; 00791 } 00792 } 00793 eZPersistentObject::replaceFieldsWithShortNames( $db, $fields, $field_array ); 00794 $field_text = ''; 00795 $i = 0; 00796 foreach ( $field_array as $field_item ) 00797 { 00798 if ( ( $i % 7 ) == 0 and 00799 $i > 0 ) 00800 $field_text .= ",\n "; 00801 else if ( $i > 0 ) 00802 $field_text .= ', '; 00803 $field_text .= $field_item; 00804 ++$i; 00805 } 00806 00807 $where_text = eZPersistentObject::conditionText( $conds ); 00808 if ( $custom_conds ) 00809 $where_text .= $custom_conds; 00810 00811 $sort_text = ""; 00812 if ( $sorts !== false and ( isset( $def["sort"] ) or is_array( $sorts ) ) ) 00813 { 00814 $sort_list = array(); 00815 if ( is_array( $sorts ) ) 00816 { 00817 $sort_list = $sorts; 00818 } 00819 else if ( isset( $def['sort'] ) ) 00820 { 00821 $sort_list = $def["sort"]; 00822 } 00823 if ( count( $sort_list ) > 0 ) 00824 { 00825 $sort_text = "\nORDER BY "; 00826 $i = 0; 00827 foreach ( $sort_list as $sort_id => $sort_type ) 00828 { 00829 if ( $i > 0 ) 00830 $sort_text .= ", "; 00831 if ( $sort_type == "desc" ) 00832 $sort_text .= "$sort_id DESC"; 00833 else 00834 $sort_text .= "$sort_id ASC"; 00835 ++$i; 00836 } 00837 } 00838 } 00839 00840 $grouping_text = ""; 00841 if ( isset( $def["grouping"] ) or ( is_array( $grouping ) and count( $grouping ) > 0 ) ) 00842 { 00843 $grouping_list = $def["grouping"]; 00844 if ( is_array( $grouping ) ) 00845 $grouping_list = $grouping; 00846 if ( count( $grouping_list ) > 0 ) 00847 { 00848 $grouping_text = "\nGROUP BY "; 00849 $i = 0; 00850 foreach ( $grouping_list as $grouping_id ) 00851 { 00852 if ( $i > 0 ) 00853 $grouping_text .= ", "; 00854 $grouping_text .= "$grouping_id"; 00855 ++$i; 00856 } 00857 } 00858 } 00859 00860 $db_params = array(); 00861 if ( is_array( $limit ) ) 00862 { 00863 if ( isset( $limit["offset"] ) ) 00864 { 00865 $db_params["offset"] = $limit["offset"]; 00866 } 00867 if ( isset( $limit['limit'] ) ) 00868 { 00869 $db_params["limit"] = $limit["limit"]; 00870 } 00871 else 00872 { 00873 $db_params["limit"] = $limit["length"]; 00874 } 00875 } 00876 00877 $sqlText = "SELECT $field_text 00878 FROM $tables" . 00879 $where_text . 00880 $grouping_text . 00881 $sort_text; 00882 $rows = $db->arrayQuery( $sqlText, 00883 $db_params ); 00884 00885 // Indicate that a DB error occured. 00886 if ( $rows === false ) 00887 return null; 00888 00889 $objectList = eZPersistentObject::handleRows( $rows, $class_name, $asObject ); 00890 return $objectList; 00891 } 00892 00893 /*! 00894 Creates PHP objects out of the database rows \a $rows. 00895 Each object is created from class \$ class_name and is passed 00896 as a row array as parameter. 00897 00898 \param $asObject If \c true then objects will be created, 00899 if not it just returns \a $rows as it is. 00900 */ 00901 static function handleRows( $rows, $class_name, $asObject ) 00902 { 00903 if ( $asObject ) 00904 { 00905 $objects = array(); 00906 if ( is_array( $rows ) ) 00907 { 00908 foreach ( $rows as $row ) 00909 { 00910 $objects[] = new $class_name( $row ); 00911 } 00912 } 00913 return $objects; 00914 } 00915 else 00916 return $rows; 00917 } 00918 00919 /*! 00920 Sets row id \a $id2 to have the placement of row id \a $id1. 00921 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00922 the calls within a db transaction; thus within db->begin and db->commit. 00923 */ 00924 static function swapRow( $table, $keys, $order_id, $rows, $id1, $id2 ) 00925 { 00926 $db = eZDB::instance(); 00927 $text = $order_id . "='" . $db->escapeString( $rows[$id1][$order_id] ) . "' WHERE "; 00928 $i = 0; 00929 foreach ( $keys as $key ) 00930 { 00931 if ( $i > 0 ) 00932 $text .= " AND "; 00933 $text .= $key . "='" . $db->escapeString( $rows[$id2][$key] ) . "'"; 00934 ++$i; 00935 } 00936 return "UPDATE $table SET $text"; 00937 } 00938 00939 /*! 00940 Returns an order value which can be used for new items in table, for instance placement. 00941 Uses \a $def, \a $orderField and \a $conditions to figure out the currently maximum order value 00942 and returns one that is larger. 00943 */ 00944 static function newObjectOrder( $def, $orderField, $conditions ) 00945 { 00946 $db = eZDB::instance(); 00947 $table = $def["name"]; 00948 $keys = $def["keys"]; 00949 $cond_text = eZPersistentObject::conditionText( $conditions ); 00950 $rows = $db->arrayQuery( "SELECT MAX($orderField) AS $orderField FROM $table $cond_text" ); 00951 if ( count( $rows ) > 0 and isset( $rows[0][$orderField] ) ) 00952 return $rows[0][$orderField] + 1; 00953 else 00954 return 1; 00955 } 00956 00957 /*! 00958 Moves a row in a database table. \a $def is the object definition. 00959 Uses \a $orderField to determine the order of objects in a table, usually this 00960 is a placement of some kind. It uses this order field to figure out how move 00961 the row, the row is either swapped with another row which is either above or 00962 below according to whether \a $down is true or false, or it is swapped 00963 with the first item or the last item depending on whether this row is first or last. 00964 Uses \a $conditions to figure out unique rows. 00965 \sa swapRow 00966 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 00967 the calls within a db transaction; thus within db->begin and db->commit. 00968 */ 00969 static function reorderObject( $def, 00970 /*! Associative array with one element, the key is the order id and values is order value. */ 00971 $orderField, 00972 $conditions, 00973 $down = true ) 00974 { 00975 $db = eZDB::instance(); 00976 $table = $def["name"]; 00977 $keys = $def["keys"]; 00978 00979 reset( $orderField ); 00980 $order_id = key( $orderField ); 00981 $order_val = $orderField[$order_id]; 00982 if ( $down ) 00983 { 00984 $order_operator = ">="; 00985 $order_type = "asc"; 00986 $order_add = -1; 00987 } 00988 else 00989 { 00990 $order_operator = "<="; 00991 $order_type = "desc"; 00992 $order_add = 1; 00993 } 00994 $fields = array_merge( $keys, array( $order_id ) ); 00995 $rows = eZPersistentObject::fetchObjectList( $def, 00996 $fields, 00997 array_merge( $conditions, 00998 array( $order_id => array( $order_operator, 00999 $order_val ) ) ), 01000 array( $order_id => $order_type ), 01001 array( "length" => 2 ), 01002 false ); 01003 if ( count( $rows ) == 2 ) 01004 { 01005 $swapSQL1 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 1, 0 ); 01006 $swapSQL2 = eZPersistentObject::swapRow( $table, $keys, $order_id, $rows, 0, 1 ); 01007 $db->begin(); 01008 $db->query( $swapSQL1 ); 01009 $db->query( $swapSQL2 ); 01010 $db->commit(); 01011 } 01012 else 01013 { 01014 $tmp = eZPersistentObject::fetchObjectList( $def, 01015 $fields, 01016 $conditions, 01017 array( $order_id => $order_type ), 01018 array( "length" => 1 ), 01019 false ); 01020 $where_text = eZPersistentObject::conditionTextByRow( $keys, $rows[0] ); 01021 $db->query( "UPDATE $table SET $order_id='" . ( $tmp[0][$order_id] + $order_add ) . 01022 "'$where_text" ); 01023 } 01024 } 01025 01026 /*! 01027 \return the definition for the object, the default implementation 01028 is to return an empty array. It's upto each inheriting class 01029 to return a proper definition array. 01030 01031 The definition array is an associative array consists of these keys: 01032 - fields - an associative array of fields which defines which database field (the key) is to fetched and how they map 01033 to object member variables (the value). 01034 - keys - an array of fields which is used for uniquely identifying the object in the table. 01035 - function_attributes - an associative array of attributes which maps to member functions, used for fetching data with functions. 01036 - set_functions - an associative array of attributes which maps to member functions, used for setting data with functions. 01037 - increment_key - the field which is incremented on table inserts. 01038 - class_name - the classname which is used for instantiating new objecs when fetching from the 01039 database. 01040 - sort - an associative array which defines the default sorting of lists, the key is the table field while the value 01041 is the sorting method which is either \c asc or \c desc. 01042 - name - the name of the database table 01043 01044 Example: 01045 \code 01046 static function definition() 01047 { 01048 return array( "fields" => array( "id" => "ID", 01049 "version" => "Version", 01050 "name" => "Name" ), 01051 "keys" => array( "id", "version" ), 01052 "function_attributes" => array( "current" => "currentVersion", 01053 "class_name" => "className" ), 01054 "increment_key" => "id", 01055 "class_name" => "eZContentClass", 01056 "sort" => array( "id" => "asc" ), 01057 "name" => "ezcontentclass" ); 01058 } 01059 \endcode 01060 */ 01061 static function definition() 01062 { 01063 return array(); 01064 } 01065 01066 static function escapeArray( $array ) 01067 { 01068 $db = eZDB::instance(); 01069 $out = array(); 01070 foreach( $array as $key => $value ) 01071 { 01072 if ( is_array( $value ) ) 01073 { 01074 $tmp = array(); 01075 foreach( $value as $valueItem ) 01076 { 01077 $tmp[] = $db->escapeString( $valueItem ); 01078 } 01079 $out[$key] = $tmp; 01080 unset( $tmp ); 01081 } 01082 else 01083 $out[$key] = $db->escapeString( $value ); 01084 } 01085 return $out; 01086 } 01087 01088 /*! 01089 \note Transaction unsafe. If you call several transaction unsafe methods you must enclose 01090 the calls within a db transaction; thus within db->begin and db->commit. 01091 */ 01092 static function updateObjectList( $parameters ) 01093 { 01094 $db = eZDB::instance(); 01095 $def = $parameters['definition']; 01096 $table = $def['name']; 01097 $fields = $def['fields']; 01098 $keys = $def['keys']; 01099 01100 $updateFields = $parameters['update_fields']; 01101 $conditions = $parameters['conditions']; 01102 01103 $query = "UPDATE $table SET "; 01104 $i = 0; 01105 $valueBound = false; 01106 01107 foreach( $updateFields as $field => $value ) 01108 { 01109 $fieldDef = $fields[ $field ]; 01110 $numericDataTypes = array( 'integer', 'float', 'double' ); 01111 if ( strlen( $value ) == 0 && 01112 is_array( $fieldDef ) && 01113 in_array( $fieldDef['datatype'], $numericDataTypes ) && 01114 array_key_exists( 'default', $fieldDef ) && 01115 !is_null( $fieldDef[ 'default' ] ) ) 01116 { 01117 $value=$fieldDef[ 'default' ]; 01118 } 01119 01120 $bindDataTypes = array( 'text' ); 01121 if ( $db->bindingType() != eZDBInterface::BINDING_NO && 01122 strlen( $value ) > 2000 && 01123 is_array( $fieldDef ) && 01124 in_array( $fieldDef['datatype'], $bindDataTypes ) 01125 ) 01126 { 01127 $value = $db->bindVariable( $value, $fieldDef ); 01128 $valueBound = true; 01129 } 01130 else 01131 $valueBound = false; 01132 01133 if ( $i > 0 ) 01134 $query .= ', '; 01135 if ( $valueBound ) 01136 $query .= $field . "=" . $value; 01137 else 01138 $query .= $field . "='" . $db->escapeString( $value ) . "'"; 01139 ++$i; 01140 } 01141 $query .= "\n" . 'WHERE '; 01142 $i = 0; 01143 foreach( $conditions as $conditionKey => $condition ) 01144 { 01145 if ( $i > 0 ) 01146 $query .= ' AND '; 01147 if ( is_array( $condition ) ) 01148 { 01149 $query .= $conditionKey . ' IN ('; 01150 $j = 0; 01151 foreach( $condition as $conditionValue ) 01152 { 01153 if ( $j > 0 ) 01154 $query .= ', '; 01155 $query .= "'" . $db->escapeString( $conditionValue ) . "'"; 01156 ++$j; 01157 } 01158 $query .= ')'; 01159 } 01160 else 01161 $query .= $conditionKey . "='" . $db->escapeString( $condition ) . "'"; 01162 ++$i; 01163 } 01164 $db->query( $query ); 01165 } 01166 01167 /*! 01168 \return the attributes for this object, taken from the definition fields and 01169 function attributes. 01170 */ 01171 function attributes() 01172 { 01173 $def = $this->definition(); 01174 $attrs = array_keys( $def["fields"] ); 01175 if ( isset( $def["function_attributes"] ) ) 01176 $attrs = array_unique( array_merge( $attrs, array_keys( $def["function_attributes"] ) ) ); 01177 if ( isset( $def["functions"] ) ) 01178 $attrs = array_unique( array_merge( $attrs, array_keys( $def["functions"] ) ) ); 01179 return $attrs; 01180 } 01181 01182 /*! 01183 \return true if the attribute \a $attr is part of the definition fields or function attributes. 01184 */ 01185 function hasAttribute( $attr ) 01186 { 01187 $def = $this->definition(); 01188 $has_attr = isset( $def["fields"][$attr] ); 01189 if ( !$has_attr and isset( $def["function_attributes"] ) ) 01190 $has_attr = isset( $def["function_attributes"][$attr] ); 01191 if ( !$has_attr and isset( $def["functions"] ) ) 01192 $has_attr = isset( $def["functions"][$attr] ); 01193 return $has_attr; 01194 } 01195 01196 /*! 01197 \return the attribute data for \a $attr, this is either returned from the member variables 01198 or a member function depending on whether the definition field or function attributes matched. 01199 */ 01200 function attribute( $attr, $noFunction = false ) 01201 { 01202 $def = $this->definition(); 01203 $fields = $def["fields"]; 01204 $functions = isset( $def["functions"] ) ? $def["functions"] : null; 01205 $attrFunctions = isset( $def["function_attributes"] ) ? $def["function_attributes"] : null; 01206 if ( $noFunction === false and isset( $attrFunctions[$attr] ) ) 01207 { 01208 $functionName = $attrFunctions[$attr]; 01209 $retVal = null; 01210 if ( method_exists( $this, $functionName ) ) 01211 { 01212 $retVal = $this->$functionName(); 01213 } 01214 else 01215 { 01216 eZDebug::writeError( 'Could not find function : "' . get_class( $this ) . '::' . $functionName . '()".', 01217 'eZPersistentObject::attribute()' ); 01218 } 01219 return $retVal; 01220 } 01221 else if ( isset( $fields[$attr] ) ) 01222 { 01223 $attrName = $fields[$attr]; 01224 if ( is_array( $attrName ) ) 01225 { 01226 $attrName = $attrName['name']; 01227 } 01228 return $this->$attrName; 01229 } 01230 else if ( isset( $functions[$attr] ) ) 01231 { 01232 $functionName = $functions[$attr]; 01233 return $this->$functionName(); 01234 } 01235 else 01236 { 01237 eZDebug::writeError( "Attribute '$attr' does not exist", $def['class_name'] . '::attribute' ); 01238 $attrValue = null; 01239 return $attrValue; 01240 } 01241 } 01242 01243 /*! 01244 Sets the attribute \a $attr to the value \a $val. The attribute must be present in the 01245 objects definition fields or set functions. 01246 */ 01247 function setAttribute( $attr, $val ) 01248 { 01249 $def = $this->definition(); 01250 $fields = $def["fields"]; 01251 $functions = isset( $def["set_functions"] ) ? $def["set_functions"] : null; 01252 if ( isset( $fields[$attr] ) ) 01253 { 01254 $attrName = $fields[$attr]; 01255 if ( is_array( $attrName ) ) 01256 { 01257 $attrName = $attrName['name']; 01258 } 01259 01260 $oldValue = null; 01261 if ( isset( $this->$attrName ) ) 01262 $oldValue = $this->$attrName; 01263 $this->$attrName = $val; 01264 if ( $oldValue === null || 01265 $oldValue !== $val ) 01266 $this->setHasDirtyData( true ); 01267 } 01268 else if ( isset( $functions[$attr] ) ) 01269 { 01270 $functionName = $functions[$attr]; 01271 $oldValue = $this->$functionName( $val ); 01272 if ( $oldValue === null or $oldValue !== $val ) 01273 $this->setHasDirtyData( true ); 01274 } 01275 else 01276 { 01277 eZDebug::writeError( "Undefined attribute '$attr', cannot set", 01278 $def['class_name'] ); 01279 } 01280 } 01281 01282 /*! 01283 \return true if the data is considered dirty and needs to be stored. 01284 \sa sync 01285 */ 01286 function hasDirtyData() 01287 { 01288 return $this->PersistentDataDirty; 01289 } 01290 01291 /*! 01292 Sets whether the object has dirty data or not. 01293 \sa hasDirtyData, sync 01294 */ 01295 function setHasDirtyData( $hasDirtyData ) 01296 { 01297 $this->PersistentDataDirty = $hasDirtyData; 01298 } 01299 01300 /*! 01301 \return short attribute name (alias) if it's defined, given attribute name otherwise 01302 */ 01303 static function getShortAttributeName( $db, $def, $attrName ) 01304 { 01305 $fields = $def['fields']; 01306 01307 if ( $db->useShortNames() && isset( $fields[$attrName] ) && array_key_exists( 'short_name', $fields[$attrName] ) && $fields[$attrName]['short_name'] ) 01308 return $fields[$attrName]['short_name']; 01309 01310 return $attrName; 01311 } 01312 01313 /// \privatesection 01314 /// Whether the data is dirty, ie needs to be stored, or not. 01315 public $PersistentDataDirty; 01316 } 01317 01318 ?>