References ב-PHP
-
@nigun אמר בReferences ב-PHP:
@yossiz
אז סתם יצא כאן לעז על משתמשי השפה
גם אחרי עשרות שנים מפתח PHP ממוצע לא אמור להיכנס לפינה הזאת.לטעמי אם זה מוביל את המפתח הבינוני לבאגים אז זה כשל (ועוד יותר גרוע כשיש באותה פונקציה חוסר אחידות בין ערכים למערכים).
לא מבין איך מפתח בכיר יכול להתייחס בסטאק בצורה כל כך לא עניינית (או שבצ'אט זה שונה?).בשבילי זה סיבה לנסות לא להיכנס לפינה ששמה הפניות.
-
למי שממש מעוניין להתעמק עד הסוף...
מישהו הביא לי קישור לעבודה אקדמית שמתמקדת בדיוק על הנקודות שהעליתי. מרתק.
(אל תדאגו, לא הצלחתי לעבור על הכל. זה יותר מיד אקדמי בשבילי. אבל הצלחתי להבין שהם ממש מעלים את הנקודות שאני העליתי, בעיקר ג' וד', א' וב' נראה שלא מפריע אותם כל כך כמו שזה מפריע אותי) -
עוד כתבה על הנושא:
http://schlueters.de/blog/archives/125-Do-not-use-PHP-references.htmlטוב, נראה לי שממש מיציתי את הנושא... מספיק כבר!
-
אופסס... כנראה שלא מיציתי את הנושא מספיק...
מצאתי שיש קומפיילר וסביבת הרצה עבור PHP שממומש בדוטנט.
ויקרא שמו בישראל Peachpie.
(אגב, הם טוענים שהמימוש בדוטנט מביא שיפורים משמעותיים בביצועים. זה כבר בשל מספיק להריץ וורדפרס ועוד כמה אפליקציות גדולות - עם ביצועים יותר טובים ממנוע ה-PHP המקורי)
כמובן שדבר ראשון רצתי לבדוק את המימוש שלהם ל-references, והנה יש להם באג
נפתח issue בגיטהאב שלהם...עריכה: פתחתי issue
https://github.com/peachpiecompiler/peachpie/issues/906 -
@yossiz מעולם לא הגעתי לבעיה שהעלת. אני משתמש פה ושם ברפרנס, זה חוסך שורות ומסתמא גם משאבים (לא יודע כמה זה משמעותי).
למה אין לי יותר מה להגיב? כי הגישה שלנו שונה. אתה חובב תיכנות ואני רק מתכנת. זה לא סותר שאתה המקצוען ואני החובבן, אבל אני מתכוון שבשבילי תיכנות הוא כלי שאחרי שלמדתי להשתמש בו וזה נותן לי מה שאני צריך, אני כבר לא מתעסק יותר מידי להבין מה ולמה, אלא פשוט משתמש בכלי לעשות מה שצריך.
אם יש דברים שחסרים לי, אשב ואלמד עד שאבין איך לפתור את הבעיה נקודתית, וזהו.יש על זה אריכות בהקדמה של החובות הלבבות, עם השאלות הנכריות מענייני גירושין...
-
אני לא הצלחתי הרבה להתעמק בנושא הזה.
אבל אציין דוגמא של משהו שאולי זה חלק מהכוונה של @yossiz
ואולי זה יפתח קצת את התאבון להבין טוב יותר את הנושא (גם אני לא יודע בזה הרבה)$rows = self::find() ->where(['ProjectID' => \common\models\GetProjectID::getProjectID()]) ->andWhere(['id' => $selection]) ->andWhere(['is', 'PaymentDetails', new \yii\db\Expression('null')])->asArray()->all(); $rows2 = []; foreach ($rows as $row){ if ($row['TotalToPaid'] > 0) { if (isset($Students[$row['StudentId']])) { $row['Bank'] = $Students[$row['StudentId']]['BankNumber']; $row['Branch'] = $Students[$row['StudentId']]['BranchNumber']; $row['AccountNumber'] = $Students[$row['StudentId']]['AccountNumber']; $row['TeudatZehut'] = $Students[$row['StudentId']]['Identity']; $row['ClientName'] = $Students[$row['StudentId']]['Family'] . ' ' . $Students[$row['StudentId']]['Name']; }else{ $row['Bank'] = ''; $row['Branch'] = ''; $row['AccountNumber'] = ''; $row['TeudatZehut'] = ''; $row['ClientName'] = ''; } $rows2[] = $row; } } return $rows2;
במקרה זה, היה לי מערך שעלי היה להוסיף נתונים לאיברים, ולקבל מערך חדש הכולל את האיברים.
אם זה היה כמו ב JS למשל הייתי עושה זאת פשוט יותר:$rows = self::find() ->where(['ProjectID' => \common\models\GetProjectID::getProjectID()]) ->andWhere(['id' => $selection]) ->andWhere(['is', 'PaymentDetails', new \yii\db\Expression('null')])->asArray()->all(); foreach ($rows as $row){ if ($row['TotalToPaid'] > 0) { if (isset($Students[$row['StudentId']])) { $row['Bank'] = $Students[$row['StudentId']]['BankNumber']; $row['Branch'] = $Students[$row['StudentId']]['BranchNumber']; $row['AccountNumber'] = $Students[$row['StudentId']]['AccountNumber']; $row['TeudatZehut'] = $Students[$row['StudentId']]['Identity']; $row['ClientName'] = $Students[$row['StudentId']]['Family'] . ' ' . $Students[$row['StudentId']]['Name']; }else{ $row['Bank'] = ''; $row['Branch'] = ''; $row['AccountNumber'] = ''; $row['TeudatZehut'] = ''; $row['ClientName'] = ''; } } } return $rows;
דהיינו כשעושים foreach, הוא לא מעביר לי את האיבר האמיתי המקורי, אלא איבר משוכפל, ששינוי בו אינו משנה כלום באיבר המקורי, ב JS לעומת זאת ניתן להעביר משתנים אפילו לפונקציות שהן משנות את האיבר המקורי.
דוגמא לזה, פונקציות שאני משתמש איתן המוןfunction setDescendantProp(obj, desc, value) { var arr = desc.split('.'); while (arr.length > 1) { obj = obj[arr.shift()]; } return obj[arr[0]] = value; } var obj = {a: {b: {c: 0}}}; var propPath = getPropPath(); // returns e.g. "a.b.c" var result = setDescendantProp(obj, propPath, 1); // obj.a.b.c will now be 1
מקור: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
אציין, שאני התחלתי עם PHP, וכשנתקלתי בפונקציה הנ"ל setDescendantProp לקח לי המון זמן לעבוד איך עובד הפלא ההוא.
-
@dovid אמר בReferences ב-PHP:
@חוקר אני בחיים לא צריך את הפונקציה הזאת...
לפני
if (!myData.ExtraMessageReadingConfirmation[ProjectID]){ myData.ExtraMessageReadingConfirmation[ProjectID] = {}; } if (!myData.ExtraMessageReadingConfirmation[ProjectID][messageId1]){ myData.ExtraMessageReadingConfirmation[ProjectID][messageId1] = {}; } if (!myData.ExtraMessageReadingConfirmation[ProjectID][messageId1][userId]){ myData.ExtraMessageReadingConfirmation[ProjectID][messageId1][userId] = {}; } myData.ExtraMessageReadingConfirmation[ProjectID][messageId1][userId]['np'] = 1;
אחרי
__.setDescendantProp(myData.ExtraMessageReadingConfirmation, ProjectID + '.' + messageId1 + '.' + userId + '.' + 'np', 1);
בצורת העבודה שלי יש לי המון אובייקטים שמכילים תתי אבוייקטים ותתי תתי וכו'.
לפני הפונקציה הייתי משתגע איך אני מגדיר ערך לאובייקט שאני לא יודע אם כבר קיים הנתיב שלו דהיינו תתי/תתי תתי/תתי תתי תתי
אם אגדיר אותו ישירות אקבל שגיאה שלא ניתן להגדיר ערך ל undefined.
באמצעות הפונקציה הנ"ל ששידרגתי אותה לצורה כזו:setDescendantProp : function (obj, desc, value) { if (!desc){return obj = value;} let arr = desc.split('.'); while (arr.length > 1) { let shift = arr.shift(); if (typeof obj[shift] === 'undefined'){obj[shift] = {}} obj = obj[shift]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } return obj[arr[0]] = value; },
אני מגדיר בקלות את הנתיב המלא, ומזין את הערך הנדרש באובייקט המקורי - הגלובאלי
-
להלן הסט המלא בשיטה זו:
getDescendantProp : function (obj, desc, returnByUndefined = false) { if (!desc){return obj;} let arr = desc.split('.'); while (arr.length && obj !== undefined) { obj = obj[arr.shift()]; } return obj; }, setDescendantProp : function (obj, desc, value) { if (!desc){return obj = value;} let arr = desc.split('.'); while (arr.length > 1) { let shift = arr.shift(); if (typeof obj[shift] === 'undefined'){obj[shift] = {}} obj = obj[shift]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } return obj[arr[0]] = value; }, addDescendantProp : function (obj, desc, value) { if (!desc){return obj = value;} let arr = desc.split('.'); while (arr.length > 1) { let shift = arr.shift(); if (typeof obj[shift] === 'undefined'){obj[shift] = {}} obj = obj[shift]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } if (!obj[arr[0]]){ obj[arr[0]] = 0; } return obj[arr[0]] += value; }, addPlusDescendantProp : function (obj, desc) { if (!desc){ if (!obj) {obj = 0} else {obj++} return obj} let arr = desc.split('.'); while (arr.length > 1) { let shift = arr.shift(); if (typeof obj[shift] === 'undefined'){obj[shift] = {}} obj = obj[shift]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } if (!obj[arr[0]]){ obj[arr[0]] = 0; } obj[arr[0]] = parseInt(obj[arr[0]]); return obj[arr[0]]++; }, addPropertyDescendantProp : function (obj, desc, key, value) { if (!desc){return obj = value;} let arr = desc.split('.'); while (arr.length > 1) { let shift = arr.shift(); if (typeof obj[shift] === 'undefined'){obj[shift] = {}} obj = obj[shift]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } if (!obj[arr[0]]){ obj[arr[0]] = {}; } obj[arr[0]][key] = value; return obj[arr[0]]; }, delDescendantProp : function (obj, desc) { if (!desc){return obj = value;} let arr = desc.split('.'); while (arr.length > 1) { obj = obj[arr.shift()]; if (typeof obj[arr[0]] === 'undefined' && arr.length > 1){obj[arr[0]] = {}} } return delete obj[arr[0]]; },
דוגמאות שימוש:
//הוספת סכום למשתנה שאינו בטוח קיים __.addDescendantProp(myData.IncomingCallMinutes, ApiDID + '.' + moment().format('YYYYMMDD'), duration); //הוספת ++ למשתנה שלא בטוח שהוא קיים let countIp = __.addPlusDescendantProp(myData.ipBlocked, clientIp + '.' + 'count'); //הוספת איבר והצבת ערך לאיבר, על משתנה שלא בטוח שהוא קיים __.addPropertyDescendantProp(id_data['Butza']['Mishnaiot'], seder_key +'.' + masecet_key +'.' + perek_key, i, [perek_obj['r' + i], __.getDescendantProp(myData.QuestionsAndAnswersMishnaiot, ProjectID + '.' + seder_key + '.' + masecet_key + '.' + perek_key + '.' + i + '.QValid') === parseInt(perek_obj['r' + i])]) //מחיקת איבר פנימי __.delDescendantProp(myData.QuestionsById, ProjectID + '.' + SelectedTrivia['id'] );
-
@חוקר אם תשתמש בספריית lodash, יש להם פונקציה שעושה את זה.
יש גם פונקציה הפוכה לקבל מאפיין מקונן כאשר אתה לא בטוח שחלק מהנתיב קיים.
אגב, זה משהו שנפתר לאחרונה עם הפיצ'ר החדש ב-JS של optional chaining@dovid לא ברור לי איך מייתרים את הצורך בפונקציה כזאת במצבים מסויימים.
למעשה אני לא מצליח להיזכר בפעם האחרונה שהשתמשתי ב-lodash.set
, כנראה שהקוד שלי בד"כ מתוכנן בצורה שלא צריך אותו. אבל אני יכול להבין שיש מצבים שחייבים את זה. -
@חוקר אמר בReferences ב-PHP:
אבל אציין דוגמא של משהו שאולי זה חלק מהכוונה של @yossiz
בדיוק למקרה כזה משתמשים ב-reference ב-PHP.
דהיינו כשעושים foreach, הוא לא מעביר לי את האיבר האמיתי המקורי, אלא איבר משוכפל, ששינוי בו אינו משנה כלום באיבר המקורי, ב JS לעומת זאת ניתן להעביר משתנים אפילו לפונקציות שהן משנות את האיבר המקורי.
רק לדייק בדברים:
ב-PHP יש 6 סוגים (נפוצים) של ערכים (ועוד 4 פחות נפוצים):- bool
- int
- float
- string
- array
- object
מתוכם, ה-5 הראשונים הם value types כלומר (אני מפשיט קצת) שהמשתנה מכיל את הערך, והשמה למשתנה חדש מעתיק את הערך.
אבל object הוא reference type. כלומר שהמשתנה מכיל מצביע לערך. השמה למשתנה חדש תעתיק רק את המצביע ולא את הערך עצמו.זה מה שגורם ל"בעיה" שאתה מציין
ב-JS יש 4 סוגי ערכים נפוצים:
- Boolean
- String
- Number
- Object
ל-object יש המון צאצאים.
שלושת הראשונים הם value types והאחרון הוא reference type. -
@dovid אמר בReferences ב-PHP:
חשבת שלא הבנתי מה עושה הפונקציה
לא עבורך!
אני חשבתי מזמן שזה יכול לעזור לאחרים וכאן הייתה הבמה.
כי לי זה היה חידוש, וכמו שציינתי בדוגמא איך שכתבתי המון שורות מסורבלות, עד שגיליתי את החידוש ההוא.
למה שאחרים לא ידעו מזה.. -
@חוקר אמר בReferences ב-PHP:
במקרה זה, היה לי מערך שעלי היה להוסיף נתונים לאיברים, ולקבל מערך חדש הכולל את האיברים.
אם זה היה כמו ב JS למשל הייתי עושה זאת פשוט יותר:@yossiz ענה לך מצוין:
@yossiz אמר בReferences ב-PHP:
בדיוק למקרה כזה משתמשים ב-reference ב-PHP.
תחליף את הקוד ל:
foreach ($rows as &$row){
זה לא מסתדר?
-
@www אמר בReferences ב-PHP:
תחליף את הקוד ל:
foreach ($rows as &$row){זה לא מסתדר?
אבל יש כאן בעיה שצריך לשים לב אליה. מה ש-@nigun הזכיר למעלה, שב-PHP לולאת
foreach
לא יוצרת scope נפרד, ולכן המשתנה$row
נשארת אחרי הלולאה מצביעה על האיבר האחרון של ה-array. שימוש חוזר בשם המשתנה תעשה דברים לא צפויים... -
@yossiz אמר בReferences ב-PHP:
אבל יש כאן בעיה שצריך לשים לב אליה. מה ש-@nigun הזכיר למעלה, שב-PHP לולאת foreach לא יוצרת scope נפרד, ולכן המשתנה $row נשארת אחרי הלולאה מצביעה על האיבר האחרון של ה-array. שימוש חוזר בשם המשתנה תעשה דברים לא צפויים...
זו בעיה שאפשר לפתור אותה בקלות. וגם בד"כ באמת לא אמורים להשתמש שוב באותו שם משתנה.
-