לוגו אתר Fresh          
 
 
  אפשרות תפריט  ראשי     אפשרות תפריט  צ'אט     אפשרות תפריט  מבזקים     אפשרות תפריט  צור קשר     חץ שמאלה ‎print ‎"Hello World!"; if‎ ‎not rules.‎know ‎then rules.‎read(); חץ ימינה  

לך אחורה   לובי הפורומים > מחשבים > תכנות ובניית אתרים
שמור לעצמך קישור לדף זה באתרי שמירת קישורים חברתיים
תגובה
 
כלי אשכול חפש באשכול זה



  #1  
ישן 05-03-2009, 04:05
  משתמש זכר dorM dorM אינו מחובר  
מנהל
 
חבר מתאריך: 26.07.08
הודעות: 6,473
PDO Explained

PDO (ר"ת PHP Data Object) זהו ממשק המגשר בין PHP למסד(י) הנתונים.
העיקרון בקיומו הוא ליצור ממשק אחיד שדרכו יהיה ניתן להתחבר לכל מסדי הנתונים ולשלוח שאילתות וכד'.
יתרון נוסף ועיקרי הוא האפשרות להרצת prepared statements אשר מספקת הרצות יעילות של שאילתות וחסינות מ- SQL Injections בלי הצורך להבריח את המידע הנכנס.

כדי להתחבר למסד נתונים מסויים, יש ליצור instance של מחלקת ה-PDO, כאשר הפרמטר הראשון שהוא dsn$ מצביע על היעד שאליו מתחברים.
ה-DSN (ר"ת Data Source Name), במקרה שלנו, יכיל את מסד הנתונים (לדוגמא mysql) ושם ה-host (לדוגמא localhost). יש גם אופציה לציין גם את מסד הנתונים שנמצא ב-connection אליו מתחברים. (שנוצר ע"י המשתמש, ולא משהו כמו mysql או oracle וכד')
לכל מסד נתונים אחר יש driver שונה שאותו PDO ייטען בהתחברות למסד הנתונים. ראה PDO Drivers.

לאחר ההתחברות באמצעות האופרטור new, מוחזר העצם של מחלקת ה-PDO.
עצם זה מייצג את החיבור למסד הנתונים. דרכו ניתן להכיר שני עצמים\מחלקות נוספים:
  • PDOStatement - העצם המייצג את המשפט שנשלח למסד הנתונים באמצעות PDO, בין אם המשפט הוא prepared statement או שאילתא.
  • PDOException - עצם המייצג שגיאה. כדי להכיר ולהבין אותו טוב יותר, חובה לדעת טוב את נושא ה- Exceptions של PHP.

כמה דברים בסיסיים שצריך לדעת:
  • הרצת שאילתא - מתבצע דרך מתודת ()PDO::query שמחזירה עצם PDOStatement.
  • הכנת משפט (prepared statement) - מתבצע דרך ()PDO::prepare אשר מחזיר עצם PDOStatement.
    באמצעות עצם זה יש לקשור ערכים לפרמטרים במשפט שהוכן, ע"י המתודות ()PDOStatement::bindValue או ()PDOStatement::bindParam.
    באותו המשפט, ניתן לייצג פרמטרים ב-2 דרכים: באמצעות סימן שאלה ( '?' ) או באמצעות התוית בפורמט הבא - 'name:' כאשר במקום name ניתן להחליף לשם אחר.
    המתודה ()PDOStatement::bindParam קושרת משתנה של PHP באמצעות reference. (לכן גם ניתן לשנות את ערך המשתנה לאחר הרצת מתודה זו)
    המתודה ()PDOStatement::bindValue קושרת ערך (מתבצע ללא reference).
  • הרצת משפט - מתבצע באמצעות ()PDOStatement::execute לאחר שהוקצה ערך לכל הפרמטרים במשפט.
  • תפיסת ערכים שהוחזרו משאילתא - עד כמה שאני זוכר כרגע ישנם ארבעה:
    • דרך מבנה הבקרה foreach שצריך להיות מופעל על עצם ה- PDOStatement שהוחזר.
      לדוגמא:
      קוד PHP:
       foreach($statement_object as $row) { echo $row['column_name'];  } 
    • דרך המתודה ()PDOStatement::fetch בצורה הבאה:
      קוד PHP:
       while ( $row $statement_object->fetch() ) { } 
    • דרך המתודה ()PDOStatement::fetchAll. זה לא מומלץ כאשר מוחזרות רשומות רבות.
    • דרך המתודה ()PDOStatement::fetchColumn שתופס ערך של טור לפי הפרמטר הראשון שהוכנס אליו, כאשר פרמטר זה מייצג את האינדקס של הטור בשאילתא.
    בעצם אני חושב שזה כל הדרכים.
  • החל מגירסה PHP 5.1, מחלקת ה-PDO נהייתה חלק בלתי נפרד מה- PHP Core.
    בגירסאות שבין PHP 5.0 לבין PHP 5.1 ניתן היה להשיג את PDO באמצעות חבילת PECL.
    אני חושב שזה לא נורא אם המערכת שתיבנו תותאם ל- PHP > 5.1.

סה"כ PDO עוזר, אבל לדעתי חסרים בו כמה דברים ויש בו גם באגים (או שזה מכוון...), כמו:
  • פונקציה להחלפת מסד נתונים.
  • הרצת פונקציית ה-SQL ששמה COUNT בשאילתא אחת שגם מנסה להשיג ערכים מטורים.
    לדוגמא, במידה ויש ערכים בטבלה test, השאילתא הבאה תחזיר לי רשומה אחת בלבד:
    קוד:
    SELECT COUNT(*), `id`, `name` FROM `test`

    בהתחלה חשבתי שניתן אולי להשתמש בפונקציה PDOStatement::rowCount() אבל אז התברר לי שעבור שאילתות SELECT - זה לא נתמך ע"י MySQL, זה לא מומלץ לשימוש וזה לא portable.
    לכן החלופה ב-SQL היא להריץ שאילתא דומה פעמיים...
    החלופה ב-PHP היא ליצור משתנה שמוסיפים לו 1 בכל איטרציה של הלולאה (במידה וצריך את המשתנה הזה רק אחרי הלולאה).
  • שימוש ב- prepared statement כל הזמן אינו מומלץ - בעיקר כאשר מריצים את השאילתא פעם אחת בלבד.
    לכן רציתי שתהיה לי אפשרות לבצע הברחה ל-input שאני מכניס לשאילתות.

    עד כמה שהצלחתי להבחין, הפונקציה היחידה של PDO שמבצעת הברחה היא ()PDO::quote. הבעיה היחידה היא שהפונקציה גם תוחמת את המשתנה עם שני גרשים ( ' ).

    זה ניתן לפיתרון בעזרת פונקציית trim (זה גם לא יקלקל מחרוזת המקודדת ב-UTF-8 כך שזה טוב עבור רובנו), אבל למה שאצטרך להריץ בשביל זה trim?

    דרך אחרת לפיתרון הבעיה היא שימוש בפונקציה המסורתית ()mysql_real_escape_string - וזה אכן פעל! וזה פעל טוב.
    רק שאני לא מבין איך זה פעל אם לא פתחתי connection לשרת MySQL באמצעות פונקציית ()mysql_connect ?
    בדוקומנטציה כתוב שבמידה והפרמטר השני לא הוכנס לפונקציה mysql_connect, אז היא תחפש חיבור שנפתח בעבר. (ואני סבור שלא פתחתי)
    אם היא לא תמצא חיבור שנפתח בעבר, אז היא תנסה לפתוח חיבור חדש באמצעות הערכים שנמצאים ב- php.ini. אצלי הערכים האלה, שב-php.ini, כולם ריקים (מחרוזת ריקה) ולכן גם במקרה הזה הפונקציה לא תצליח להתחבר.
    לכן מה שנותר להניח זה שהחיבור ש-PDO פתח לשרת ה-MYSQL זמין גם עבור הפונקציה mysql_real_escape_string ! אבל כיצד? ניסיתי להקצות את העצם של מחלקת ה-PDO בפרמטר השני של הפונקציה, ונכתב לי שגיאה שהיא צריכה לקבל resource ולא object... בשבילי זה עדיין תעלומה, אשמח להבהרה.

עשיתי כמה קבצי בדיקות של דברים שתוכלו לראותם בעצמכם, ולעשות גם את הבדיקות בעצמכם:
php_basics.php
php_extends.php


צריך להפעיל קודם את הקובץ PDO_basics.php ואח"כ את PDO_extends.php.
ב-PDO_extends.php נתתי דוגמא (שראיתי את העיקרון שלה במקומות אחרים) כיצד ניתן להרחיב את מחלקת ה-PDO של PHP ובכך להוסיף אפשרויות שיוגדרו ע"י המתכנת.
בקובץ זה יש גם דוגמא של הברחות של פונקציות שונות. (זה מציג גם את ההברחה הגרועה של quote)

בסוף למרות היתרונות של PDO, יש לו גם חסרונות מוזרים, ולכן ממש קשה לי להחליט באיזו מחלקה אבחר עבור ניהול מסד הנתונים...

קישורי מידע נוספים (שימו לב לתאריכים):
סוף דבר הכל נשמע...
אשמח להערות ובמיוחד הארות, תודה.

נערך לאחרונה ע"י dorM בתאריך 05-03-2009 בשעה 04:09.
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
  #4  
ישן 05-03-2009, 11:27
צלמית המשתמש של fealls
  fealls fealls אינו מחובר  
 
חבר מתאריך: 11.03.07
הודעות: 1,668
בתגובה להודעה מספר 1 שנכתבה על ידי dorM שמתחילה ב "PDO Explained"

כל הכבוד שהבאת את כל המידע והסברת את הנושא ממש טוב...

התעסקתי בשאלה של PDO או MYSQLi המון לפני לא כל כך הרבה זמן, והגעתי למסקנה שאני אמשיך לעבוד עם MySQLi.
אחת הסיבות העיקריות היא מהירות, שכמו שאפשר לראות ברוב ה BENCHMARKS ש- PDO איטי בהרבה.
עוד סיבה עיקרית היא שאין צורך בעוד שכבת עיבוד מידע כדי לעבוד עם המסד נתונים, אלא אם באמת - וכאן הגדולה של PDO - אתה הולך להחליף מסדי נתונים על בסיס קבוע.
אין כמו MySQLi לעבודה עם MySQL.

אגב, עשיתי כמה שינויים לclass של ה MySQLi כדי שיתאים לצרכים שלי ולצורת העבודה שלי - וזה משלב לי את ההגנות מSQL INJECTIONS של MySQLi והנוחות של ביצוע שאילתות בסיסי של MySQL... (אגב עוד משהו שנהדר בMySQLi זה שהוא קיים גם בצורת OOP מה שמאפשר שינויים בלי להתחיל ליצור עוד פונקציות מסביב לכל מה שכבר יש...)
השתמשתי בכמה דברים שראיתי בכל מיני קבוצות דיון ובאתר php.net והוספתי קצת שינויים שלי, כמו הצורך בלהשאיר את השאילתה פתוחה ולא לסגור אותה (->close()) אך כמו כן תמיד לסגור אותה בסוף שימוש כדי להמשיך לשאילתה הבאה.

קוד PHP:
<?php
class mysqli_extended extends mysqli {
    public function 
__construct($dbHost$dbUsername$dbPassword$dbDatabase)
    {
        
parent::__construct($dbHost$dbUsername$dbPassword$dbDatabase);
    }
  public function 
prepare($query)
  {
        
$stmt = new stmt_extended($this$query);
        return 
$stmt;
    }
  public function 
execQuery($call,$types='',$params=array()) {
    
$stmt $this->prepare($call);
    if (
count($params) > 0) {
        
$bind_names[] = $types;
        for (
$i=0$i<count($params);$i++) {
              
$bind_name 'bind' $i;
             $
$bind_name $params[$i];
          
$bind_names[] = &$$bind_name;
           }
           
call_user_func_array(array($stmt,'bind_param'),$bi  nd_names);
      }
    
$stmt->execute();
    
$stmt->store_result();
    return 
$stmt;
  }
}
class 
stmt_extended extends mysqli_stmt
{
    protected 
$varsBound false;
    protected 
$results;
    public function 
__construct($link$query)
    {
        
parent::__construct($link$query);
    }
    public function 
fetch_assoc()
    {
        
// checks to see if the variables have been bound, this is so that when
        //  using a while ($row = $this->stmt->fetch_assoc()) loop the following
        // code is only executed the first time
        
if (!$this->varsBound) {
            
$meta $this->result_metadata();
            while (
$column $meta->fetch_field()) {
                
$columnName str_replace(' ''_'$column->name);
                
$bindVarArray[] = &$this->results[$columnName];
            }
            
call_user_func_array(array($this'bind_result'), $bindVarArray);
            
$this->varsBound true;
        }
        if (
$this->fetch() != null) {
            foreach (
$this->results as $k => $v)
                
$results[$k] = $v;
            return 
$results;
        } else {
            
// this executes on the last time fetch_assoc() is being called in a loop; as I have no more use of the prepared statement, close it.
            
$this->close();
            return 
null;
        }
    }
    public function 
num_rows()
    {
        
// altered num_rows to close the prepared statement after retrieving number of rows, as my execQuery does not ->close().
        // (can always still use ->num_rows before a ->fetch_assoc() to retrieve it without closing.)
        
$num_rows $this->num_rows;
        
$this->close();
        return 
$num_rows;
    }
}
?>


אני יוצר חיבור בצורה הבאה:
קוד PHP:
 $mysqli = new mysqli_extended('server''user''password''database''); 


ומריץ שאילתות בצורה הבאה:
קוד PHP:
 $result $mysqli->execQuery('SELECT * FROM table WHERE id=? AND name=?''is', array($_REQUEST['id'], $_REQUEST['name']));
$num_rows $result->num_rows;
for (
$i 0$row $result->fetch_assoc(); $i++) {
echo 
'header: '$row['header'] .' message: '$row['message'];



אם אין לי שימוש בfetch_assoc() בשאילתה כלשהי אלא רק למצוא את מספר השורות שנבחרו, אפשרי פשוט להשתמש בצורה הבאה:
קוד PHP:
 $num_rows $mysqli->execQuery('SELECT 1 FROM table WHERE id=? AND name=?''is', array($_REQUEST['id'], $_REQUEST['name']))->num_rows(); 

ו num_rows() כבר ידאג לסגור (->close()) את השאילתה, וכמובן להחזיר את כמות השורות שנבחרו.

אם אין צורך בלולאה, לדוגמא אם התוצר אמור להיות רק שורה אחת או כלום (LIMIT 1) ואין צורך בכמות השורות הקיימת - אפשר פשוט לבצע את השאילתה בצורה הבאה:
קוד PHP:
 $row $mysqli->execQuery('SELECT * FROM table WHERE id=? AND name=?''is', array($_REQUEST['id'], $_REQUEST['name']))->fetch_assoc(); 


אם יש למישהו הערות או הארות לגבי צורת השימוש שלי ב MySQLi אני אשמח לשמוע, פעם ראשונה שאני מציג את זה לפני אנשים אחרים...
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
  #6  
ישן 05-03-2009, 11:53
צלמית המשתמש של fealls
  fealls fealls אינו מחובר  
 
חבר מתאריך: 11.03.07
הודעות: 1,668
בתגובה להודעה מספר 5 שנכתבה על ידי dorM שמתחילה ב "תודה רבה על ההוספה! אני שמח..."

ציטוט:
במקור נכתב על ידי dorM
תודה רבה על ההוספה!
אני שמח שמרחיבים את הדיון לממשקים אחרים.

אכן בבדיקות של ה- benchmarks התברר ש- MySQLi מהירה מכולם.
ולכן התכוונתי להכיר אותה היום. אולי אני יוסיף כמה מילים אחרי שאכיר את MySQLi.

תודה על תרומת הקוד!

אין בעד מה, אני שמח אם חסכתי לך ו\או לאחרים קצת זמן וכאב ראש
ויותר מאשמח לשמוע ממך ומאחרים בנושא אחרי התנסות שלכם...

ומכאן לשאלה חשובה: לא יצא לי לעבוד אף פעם על פרוייקט רציני לחברה או מישהו אחר, חוץ מפרוייקט ממש קטן שהיה ממש מזמן, ורציתי לשאול - כמה באמת מתבקשים ליצור קוד שיהיה portable לכל סוג של מסד נתונים קיים?
אני עדיין במחשבה ש-mysql הוא סוג של סטנדרט...
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
  #7  
ישן 06-03-2009, 14:17
  משתמש זכר dorM dorM אינו מחובר  
מנהל
 
חבר מתאריך: 26.07.08
הודעות: 6,473
בתגובה להודעה מספר 6 שנכתבה על ידי fealls שמתחילה ב "[QUOTE=dorM]תודה רבה על..."

אוקי אז קראתי את הקוד ויש כמה שאלות:

1. למה להשתמש ב- prepared statement במקום להריץ שאילתא כרגיל? לפי מה שהבנתי, הרצה של שאילתא פעם אחת אמורה להיות יעילה יותר באמצעות ההרצה הרגילה של השאילתא, ולא עם prepared statements.

2. במתודה execQuery הרצת את המתודה store_result השייכת לאובייקט ה-statement. למה לא לבצע fetch בלעדיה? store_result שומרת את התוצאות ב- buffer.

3. במתודה execQuery אני חושב שזה היה מיותר לעשות את הלולאת for ההיא, והיית יכול לכתוב:

במקום:
קוד PHP:
 public function execQuery($call,$types='',$params=array()) { 

את:
קוד PHP:
 public function execQuery($call,$types='', &$params=array()) { 


ואז את החלק הזה:
קוד PHP:
 call_user_func_array(array($stmt,'bind_param'),$bi  nd_names); 


תחליף בחלק הזה:
קוד PHP:
 call_user_func_array(array($stmt,'bind_param'),$pa  rams); 


4. בנוגע למחלקה שמרחיבה את מחלקת ה-statements - אני חושב שאפשר היה לוותר עליה כיוון שמערכת לא אמורה לכלול הרבה prepared statements לפי דעתי...
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
  #15  
ישן 08-03-2009, 00:51
  משתמש זכר dorM dorM אינו מחובר  
מנהל
 
חבר מתאריך: 26.07.08
הודעות: 6,473
Benchmark של inserts - הממשק mysqli לוקח בקטנה...
בתגובה להודעה מספר 1 שנכתבה על ידי dorM שמתחילה ב "PDO Explained"

עשיתי בדיקות benchmark לשאילתות INSERT בלבד, והשוותי בין PDO, MySQL ו- MySQLi בסוגי שאילתות שונות.

את קבצי ה-benchmark לקחתי מהעמוד הבא:
http://dealnews.com/developers/php-mysql.html
זה נמצא הכי למטה שם בעמוד.
עשיתי כמה שינויים כדי שהשרת MySQL לא יקרוס תחת עומס...

התוצאות לפניכם:
קוד:
mysql_extended_inserts.php 0.065 ~ 0.067 [sec] mysql_normal_inserts.php 0.2 ~ 0.21 [sec] mysqli_extended_inserts.php 0.065 ~ 0.067 [sec] mysqli_normal_inserts.php 0.21 ~ 0.22 [sec] mysqli_prepared_inserts.php 0.187 ~ 0.19 [sec] pdo_extended_inserts.php 0.068 ~ 0.07 [sec] pdo_normal_inserts.php 0.21 ~ 0.22 [sec] pdo_prepared_inserts.php 0.21 ~ 0.215 [sec]


נבדק על:
קוד:
MySQL server version 5.1.25 PHP MySQL library version 5.0.51a PHP version 5.2.6 Windows XP SP2


היו בדיקות שהיה קשה להחליט במדויק על התוצאה הסופית, בגלל "הקפיצות" במספרים כאשר הרצתי שוב ושוב את אותו הקוד.

לא עשיתי benchmark על שאילתות SELECT.

אני מניח שהתוצאות די הגיוניות מאחר ו-PDO מתאים למסדי נתונים רבים, ולעומת זאת mysqli הוא ספציפית עבור MYSQL.
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
  #16  
ישן 09-03-2009, 20:46
  משתמש זכר dorM dorM אינו מחובר  
מנהל
 
חבר מתאריך: 26.07.08
הודעות: 6,473
benchmark של SELECT
בתגובה להודעה מספר 15 שנכתבה על ידי dorM שמתחילה ב "Benchmark של inserts - הממשק mysqli לוקח בקטנה..."

זה ממש קשה לעשות benchmark ועוד במצבים האלה, כי יש קפיצות לכאן ולכאן של הזמן הנמדד. קשה להחליט.
לכן אם אין הפרש משמעותי בין תוצאות - קבלו את זה בתור תיקו...

הינה התוצאות שיצאו בבדיקה:
קוד:
mysql_normal_selects.php 0.013229 ~ 0.016034 [sec] mysqli_normal_selects.php 0.013813 ~ 0.015676 [sec] mysqli_prepared_selects.php 0.013649 ~ 0.016475 pdo_normal_selects.php 0.025198 ~ 0.030017 [sec] pdo_prepared_selects.php ~ 0.015010 ~ [sec]


אין הבדל משמעותי בין הבדיקות, מלבד pdo_normal_selects. כלומר שעדיף במקרה של SELECT להשתמש תמיד ב-prepared.
בקובץ pdo_prepared_selects.php כתבתי שהזמן הוא ~ X ~ כלומר הזמן נע בין הזמן שהוא X (הוא חג סביבו...).

אני חושב שבכל מצב כדאי לכתוב קוד שבעתיד יהיה אפשר להתאימו לממשקים שונים בקלות. לכן בינתיים "לא יהיה נורא" אם תישארו עם הממשק שכבר בחרתם.
ד"א אני זוכר שנכתב ש- PHP 5.3 שופר משמעותית בזמן ריצה, ב-20%, לכן אולי השיפור ניכר גם ב-PDO ו\או mysqli....

כמו שכתבתי קודם, עשיתי את הבדיקות על:
קוד:
MySQL server version 5.1.25 PHP MySQL library version 5.0.51a PHP version 5.2.6 Windows XP SP2
תגובה ללא ציטוט תגובה עם ציטוט חזרה לפורום
תגובה

כלי אשכול חפש באשכול זה
חפש באשכול זה:

חיפוש מתקדם
מצבי תצוגה דרג אשכול זה
דרג אשכול זה:

מזער את תיבת המידע אפשרויות משלוח הודעות
אתה לא יכול לפתוח אשכולות חדשים
אתה לא יכול להגיב לאשכולות
אתה לא יכול לצרף קבצים
אתה לא יכול לערוך את ההודעות שלך

קוד vB פעיל
קוד [IMG] פעיל
קוד HTML כבוי
מעבר לפורום



כל הזמנים המוצגים בדף זה הם לפי איזור זמן GMT +2. השעה כעת היא 11:26

הדף נוצר ב 0.10 שניות עם 10 שאילתות

הפורום מבוסס על vBulletin, גירסא 3.0.6
כל הזכויות לתוכנת הפורומים שמורות © 2024 - 2000 לחברת Jelsoft Enterprises.
כל הזכויות שמורות ל Fresh.co.il ©

צור קשר | תקנון האתר