Các hàm lồng nhau trong PHP để làm gì?


79

Trong JavaScript, các hàm lồng nhau rất hữu ích: bao đóng, phương thức riêng tư và những gì bạn có ..

Các hàm PHP lồng nhau để làm gì? Có ai sử dụng chúng không và để làm gì?

Đây là một cuộc điều tra nhỏ tôi đã thực hiện

<?php
function outer( $msg ) {
    function inner( $msg ) {
        echo 'inner: '.$msg.' ';
    }
    echo 'outer: '.$msg.' ';
    inner( $msg );
}

inner( 'test1' );  // Fatal error:  Call to undefined function inner()
outer( 'test2' );  // outer: test2 inner: test2
inner( 'test3' );  // inner: test3
outer( 'test4' );  // Fatal error:  Cannot redeclare inner()

1
Tôi có thể thề rằng tôi đã đọc rằng hỗ trợ cho điều này đã bị loại bỏ trong PHP6 nhưng tôi không thể tìm thấy nó ở đâu.
Greg

2
@greg Tôi nghĩ rằng toàn bộ kế hoạch cho PHP6 đã được thực hiện?
James

Chúng tuyệt vời cho các chức năng lớn - tổ chức đệ quy sorta
JVE999

Bạn cũng có những đóng góp trong PHP, không phải lo lắng.
Gralgrathor

Câu trả lời:


87

Về cơ bản thì không có. Tôi luôn coi điều này như một tác dụng phụ của trình phân tích cú pháp.

Eran Galperin đã nhầm lẫn khi nghĩ rằng những chức năng này là riêng tư. Chúng chỉ đơn giản là không được khai báo cho đến khi outer()được chạy. Chúng cũng không thuộc phạm vi tư nhân; chúng gây ô nhiễm phạm vi toàn cầu, mặc dù bị trì hoãn. Và như một cuộc gọi lại, lệnh gọi lại bên ngoài vẫn chỉ có thể được gọi một lần. Tôi vẫn không thấy nó hữu ích như thế nào khi áp dụng nó trên một mảng, rất có thể sẽ gọi bí danh nhiều lần.

Ví dụ 'thế giới thực' duy nhất mà tôi có thể tìm ra là cái này , chỉ có thể chạy một lần và có thể được viết lại sạch hơn, IMO.

Cách sử dụng duy nhất mà tôi có thể nghĩ đến, là để các mô-đun gọi một [name]_includephương thức, đặt một số phương thức lồng nhau trong không gian chung, kết hợp với

if (!function_exists ('somefunc')) {
  function somefunc() { }
}

Séc.

OOP của PHP rõ ràng sẽ là lựa chọn tốt hơn :)


9
Thật đấy. Tệ thật.
Tony Arkles 29/09/09

1
Liên kết ví dụ tuyệt vời. Tôi nên bắt đầu thực hiện điều đó thay vì kế thừa!
zanlok

giống như defkhai báo trong Ruby
user102008

1
Mặc dù chúng không chính xác là các hàm riêng tư, chúng vẫn KHÔNG thể được gọi là
BẤT CỨ

2
Nếu không có hành vi này, tự động tải sẽ không hoạt động. Nếu các khai báo bên trong một hàm bằng cách nào đó là riêng tư, thì bao gồm / yêu cầu được thực hiện bởi trình xử lý tự động nạp của bạn sẽ không làm gì cả.
khai mạc

87

Nếu bạn đang sử dụng PHP 5.3, bạn có thể có thêm hành vi giống Javacript với một hàm ẩn danh:

<?php
function outer() {
    $inner=function() {
        echo "test\n";
    };

    $inner();
}

outer();
outer();

inner(); //PHP Fatal error:  Call to undefined function inner()
$inner(); //PHP Fatal error:  Function name must be a string
?>

Đầu ra:

test
test

11
1 cho thực sự trả lời một (cơ bản) chủ đề chức năng với một câu trả lời chức năng, và không OOP
Peter chủ

Tác giả câu hỏi nên cập nhật câu hỏi được chấp nhận cho câu hỏi này. Đây là những gì thực sự trả lời câu hỏi cho đến thời điểm của tôi.

9

[Viết lại theo nhận xét của @PierredeLESPINAY.]

Nó không chỉ là một tác dụng phụ mà thực sự là một tính năng rất hữu ích để sửa đổi động logic của chương trình của bạn. Nó có từ những ngày PHP thủ tục, nhưng cũng có thể hữu ích với các kiến ​​trúc OO, nếu bạn muốn cung cấp các triển khai thay thế cho một số hàm độc lập theo cách đơn giản nhất có thể. (Mặc dù OO là lựa chọn tốt hơn trong hầu hết thời gian, nhưng đó là một lựa chọn, không phải là nhiệm vụ và một số nhiệm vụ đơn giản không cần thêm mẹo.)

Ví dụ: nếu bạn tải động / có điều kiện các plugin từ khuôn khổ của mình và muốn làm cho cuộc sống của các tác giả plugin trở nên siêu dễ dàng, bạn có thể cung cấp các triển khai mặc định cho một số chức năng quan trọng mà plugin đã không ghi đè:

<?php // Some framework module

function provide_defaults()
{
    // Make sure a critical function exists:
    if (!function_exists("tedious_plugin_callback"))
    {
        function tedious_plugin_callback()
        {
        // Complex code no plugin author ever bothers to customize... ;)
        }
    }
}

2
Tuy nhiên, theo OP, phạm vi chức năng lồng nhau dường như không giới hạn các chức năng container ...
Pierre de LESPINAY

1
@PierredeLESPINAY: Rất tiếc, rất đúng, cảm ơn rất nhiều vì đã chỉ ra! : -o tôi đã cập nhật (viết lại) câu trả lời cho phù hợp. (Tức là nó vẫn là một tính năng rất tiện dụng, nhưng vì một lý do hoàn toàn khác.)
Sz.

7

Các hàm được xác định trong các hàm Tôi không thể thấy nhiều sử dụng nhưng các hàm được xác định theo điều kiện thì tôi có thể. Ví dụ:

if ($language == 'en') {
  function cmp($a, $b) { /* sort by English word order */ }
} else if ($language == 'de') {
  function cmp($a, $b) { /* sort by German word order; yes it's different */ }
} // etc

Và sau đó tất cả những gì mã của bạn cần làm là sử dụng hàm 'cmp' trong những thứ như lệnh gọi usort () để bạn không phải kiểm tra ngôn ngữ bừa bãi trên toàn bộ mã của mình. Bây giờ tôi chưa làm điều này nhưng tôi có thể thấy các lý lẽ để làm điều đó.


1
Trước đây, chúng ta sẽ gọi đây là mã tự sửa đổi. Một công cụ tuyệt vời, nhưng nguy hiểm như GOTO tình trạng lạm dụng ...
Killroy

2
Ý tưởng XẤU. Tốt hơn: sử dụng OO và không xâm nhập vào các chi tiết của công cụ viết kịch bản.
zanlok

1
Hãy lưu ý - có thể bỏ đặt các hàm ẩn danh được gán cho biến.
BF

4

Tất cả những điều đã nói ở trên, người ta có thể chỉ cần tạo một hàm lồng nhau để thay thế một số mã được bản địa hóa, lặp lại trong một hàm (sẽ chỉ được sử dụng bên trong hàm mẹ). Một chức năng ẩn danh là một ví dụ hoàn hảo về điều này.

Một số người có thể nói chỉ tạo các phương thức riêng tư (hoặc các khối mã nhỏ hơn) trong một lớp, nhưng điều đó đang làm xáo trộn vùng nước khi một nhiệm vụ cực kỳ cụ thể (dành riêng cho cấp chính) cần được mô-đun hóa, nhưng không nhất thiết phải có sẵn cho phần còn lại của một lớp học. Tin tốt là nếu hóa ra bạn cần chức năng đó ở một nơi khác, thì cách khắc phục khá sơ đẳng (chuyển định nghĩa đến vị trí trung tâm hơn).

Nói chung, sử dụng JavaScript làm tiêu chuẩn để đánh giá các ngôn ngữ lập trình dựa trên C khác là một ý tưởng tồi. JavaScript chắc chắn là con vật riêng của nó khi so sánh với PHP, Python, Perl, C, C ++ và Java. Tất nhiên, có rất nhiều điểm tương đồng chung, nhưng các chi tiết thực tế, phức tạp (tham khảo JavaScript: Hướng dẫn Định nghĩa, Phiên bản thứ 6, Chương 1-12 ), khi được chú ý đến, hãy làm cho JavaScript cốt lõi trở nên độc đáo, đẹp mắt, khác biệt, đơn giản và phức tạp cùng một lúc. Đó là hai xu của tôi.

Nói rõ hơn, tôi không nói các hàm lồng nhau là riêng tư. Chỉ cần lồng ghép đó có thể giúp tránh lộn xộn khi một thứ gì đó nhỏ cần được mô-đun hóa (và chỉ cần thiết bởi hàm cha).


2

Tất cả php của tôi là OO, nhưng tôi thấy sử dụng cho các hàm lồng nhau, đặc biệt khi hàm của bạn là đệ quy và không nhất thiết phải là một đối tượng. Có nghĩa là, nó không được gọi bên ngoài hàm mà nó được lồng vào, nhưng là đệ quy và sau đó cần phải là một hàm.

Có rất ít điểm trong việc tạo ra một phương pháp mới để sử dụng nhanh chóng một phương pháp khác. Đối với tôi, đó là đoạn mã vụng về và không phải là vấn đề của OO. Nếu bạn sẽ không bao giờ gọi hàm đó ở bất kỳ đâu khác, hãy lồng nó vào.


1
Bạn đang kiếm được khá nhiều tiền, nhưng tôi nghĩ một ví dụ tốt hơn sẽ là khi khai báo các hàm gọi lại cho array_filter (), array_map (), preg_replace_callback (), uasort () và những thứ tương tự. Tôi sử dụng các hàm này với tần suất hợp lý và hiếm khi tôi cần gọi lại mà tôi đang khai báo bên ngoài phương thức OOP mà tôi đang gọi nó từ đó, vì vậy cảm giác sạch hơn rất nhiều để tránh làm ô nhiễm không gian tên chung hoặc thậm chí lớp với hàm gọi lại . Và cuối cùng tôi có thể làm điều đó với PHP 5.3 (như được giải thích trong câu trả lời của user614643)!
Derek,

1

Trong cách gọi webservice, chúng tôi nhận thấy nó có chi phí thấp hơn nhiều (bộ nhớ và tốc độ) bao gồm động theo kiểu lồng nhau, các hàm riêng lẻ trên các thư viện chứa đầy 1000 hàm. Ngăn xếp cuộc gọi điển hình có thể có độ sâu từ 5-10 cuộc gọi chỉ yêu cầu liên kết động hàng chục tệp 1-2kb sẽ tốt hơn là bao gồm cả megabyte. Điều này được thực hiện chỉ bằng cách tạo một gói hàm sử dụng nhỏ yêu cầu. Các hàm bao gồm được lồng trong các hàm phía trên ngăn xếp cuộc gọi. Hãy xem xét nó trái ngược với các lớp có đầy 100 chức năng không được yêu cầu trong mỗi lần gọi dịch vụ web nhưng cũng có thể sử dụng các tính năng tải lười sẵn có của php.


1

nếu bạn đang ở php 7 thì hãy xem phần này: Việc triển khai này sẽ cung cấp cho bạn một ý tưởng rõ ràng về hàm lồng nhau. Giả sử chúng ta có ba hàm (too (), boo () và zoo ()) được lồng trong hàm foo (). boo () và zoo () có cùng một hàm lồng nhau được đặt tên là xoo (). Bây giờ trong đoạn mã này, tôi đã nhận xét rõ ràng các quy tắc của các hàm lồng nhau.

   function foo(){
        echo 'foo() is called'.'<br>';
        function too(){
            echo 'foo()->too() is called'.'<br>';
        }
        function boo(){
            echo 'foo()->boo() is called'.'<br>';
            function xoo(){
                echo 'foo()->boo()->xoo() is called'.'<br>';
            }
            function moo(){
                echo 'foo()->boo()->moo() is called'.'<br>';
            }
        }
        function zoo(){
            echo 'foo()->zoo() is called'.'<br>';
            function xoo(){     //same name as used in boo()->xoo();
                echo 'zoo()->xoo() is called'.'<br>';
            }
        #we can use same name for nested function more than once 
        #but we can not call more than one of the parent function
        }
    }

/****************************************************************
 * TO CALL A INNER FUNCTION YOU MUST CALL OUTER FUNCTIONS FIRST *
 ****************************************************************/
    #xoo();//error: as we have to declare foo() first as xoo() is nested in foo()

    function test1(){
        echo '<b>test1:</b><br>';
        foo(); //call foo()
        too();
        boo();
        too(); // we can can a function twice
        moo(); // moo() can be called as we have already called boo() and foo()
        xoo(); // xoo() can be called as we have already called boo() and foo()
        #zoo(); re-declaration error
        //we cannont call zoo() because we have already called boo() and both of them have same named nested function xoo()
    }

    function test2(){
        echo '<b>test2:</b><br>';
        foo(); //call foo()
        too();
        #moo(); 
        //we can not call moo() as the parent function boo() is not yet called
        zoo(); 
        xoo();
        #boo(); re-declaration error
        //we cannont call boo() because we have already called zoo() and both of them have same named nested function xoo()

    }

Bây giờ nếu chúng ta gọi test1 (), đầu ra sẽ là:

test1:
foo() is called
foo()->too() is called
foo()->boo() is called
foo()->too() is called
foo()->boo()->moo() is called
foo()->boo()->xoo() is called

nếu chúng ta gọi test2 (), đầu ra sẽ là:

test2:
foo() is called
foo()->too() is called
foo()->zoo() is called
zoo()->xoo() is called

Nhưng chúng ta không thể gọi đồng thời cả text1 () và test2 () để tránh lỗi khai báo lại


Điều này sẽ dễ đọc và dễ hiểu hơn nếu các tên hàm phản ánh một số đặc điểm riêng của từng hàm, thay vì các tên tùy ý, có vần điệu, trông giống nhau. Nó là khó hiểu và khó theo dõi. Việc chọn tên giúp chúng tôi theo dõi vị trí của mỗi tên sẽ làm cho người dùng này thân thiện và giảm tải nhận thức cần thiết để đọc và hiểu. Tôi không có thời gian hoặc ý chí trừng phạt để làm được điều đó thông qua bài đăng này, mặc dù bạn có thể có một điểm tuyệt vời ẩn trong đó, tôi nghi ngờ rằng sẽ có ít người xung quanh để khai quật nó. Đọc SO không phải là một công trình nghiên cứu. KISS
SherylHohman

0

Tôi biết đây là một bài viết cũ nhưng tôi sử dụng các hàm lồng nhau để cung cấp một cách tiếp cận gọn gàng và ngăn nắp cho một cuộc gọi đệ quy khi tôi chỉ cần chức năng cục bộ - ví dụ: để xây dựng các đối tượng phân cấp, v.v. (rõ ràng bạn cần phải cẩn thận chỉ có hàm cha đã gọi một lần):

function main() {
    // Some code

    function addChildren ($parentVar) {
        // Do something
        if ($needsGrandChildren) addChildren ($childVar);
    }
    addChildren ($mainVar); // This call must be below nested func

    // Some more code
}

Một điểm lưu ý trong php so với JS chẳng hạn là lệnh gọi hàm lồng nhau cần được thực hiện sau, tức là bên dưới, khai báo hàm (so với JS nơi lệnh gọi hàm có thể ở bất kỳ đâu trong hàm mẹ


0

Tôi chỉ thực sự sử dụng đặc tính này khi nó hữu ích để thực thi một hàm đệ quy nhỏ bên trong một hàm chính, phân loại hơn, nhưng không muốn chuyển nó sang một tệp khác vì nó là cơ bản cho hoạt động của một quy trình chính. Tôi nhận thấy có những cách "thực hành tốt nhất" khác để thực hiện việc này, nhưng tôi muốn đảm bảo rằng các nhà phát triển của tôi nhìn thấy chức năng đó mỗi khi họ nhìn vào trình phân tích cú pháp của tôi, đó có thể là những gì họ nên sửa đổi ...


-1

Các hàm lồng nhau rất hữu ích trong Ghi nhớ (kết quả của hàm lưu vào bộ nhớ đệm để cải thiện hiệu suất).

<?php
function foo($arg1, $arg2) {
    $cacheKey = "foo($arg1, $arg2)";
    if (! getCachedValue($cacheKey)) {
        function _foo($arg1, $arg2) {
            // whatever
            return $result;
        }
        $result = _foo($arg1, $arg2);
        setCachedValue($cacheKey, $result);
    }
    return getCachedValue($cacheKey);
}
?>

3
Tôi thích ý tưởng này về nguyên tắc, tuy nhiên, tôi không nghĩ rằng điều này sẽ hoạt động trên các hàm chấp nhận các đối số và bạn đang lưu vào bộ nhớ đệm dựa trên các args này. Khi hàm được gọi lần thứ hai, với các giá trị khác nhau , kết quả sẽ không được lưu vào bộ nhớ cache và bạn sẽ cố gắng khai báo lại _foo()sẽ dẫn đến lỗi nghiêm trọng.
MrWhite

-1

Các hàm lồng nhau rất hữu ích nếu bạn muốn hàm lồng nhau sử dụng một biến đã được khai báo trong hàm mẹ.

<?php
ParentFunc();
function ParentFunc()
{
  $var = 5;
  function NestedFunc()
  {
    global $var;
    $var = $var + 5;
    return $var;
  };
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
  echo NestedFunc()."<br>";
}
?>

2
Đây là cách bạn KHÔNG BAO GIỜ nên làm.
Denis V

1
@fiddler vì NestedFunc không thực sự được lồng vào nhau, nó phát triển ra toàn cầu.
Denis V,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.