Memory Leak in plugin action

时间:2018-12-11 作者:treyBake

我制作了一个插件,基本上可以读取CSV文件并将数据导入相关表。

但是,该操作似乎会产生错误:

致命错误:/var/www/proj/wp includes/functions中允许的134217728字节内存已耗尽(试图分配6501588字节)。php在线

这让我在函数中找到了这段代码。php:

function wp_ob_end_flush_all() {
    $levels = ob_get_level();
    for ($i=0; $i<$levels; $i++)
        ob_end_flush();
}
我在谷歌上搜索了一下,发现了两种流行的解决方案,但这两种方案似乎都不起作用。

解决方案1:禁用zlib-这已被禁用
解决方案2:remove_action(\'shutdown\', \'wp_ob_end_flush_all\', 1);

解决方案2仍然存在错误,但没有消息,这并不理想。

这是导致错误的脚本:

<?php
    ini_set(\'display_startup_errors\', 1);
    ini_set(\'display_errors\', 1);
    error_reporting(-1);

    # load core wp fnc
    require_once $_SERVER[\'DOCUMENT_ROOT\']. \'/wp-load.php\';
    # load db functions
    require_once $_SERVER[\'DOCUMENT_ROOT\']. \'/wp-admin/includes/upgrade.php\';
    # load admin fnc
    require_once $_SERVER[\'DOCUMENT_ROOT\']. \'/wp-content/plugins/vendor-module/admin/inc/admin-functions.php\';

    global $wpdb;

    $admin_functions = new vendor_module_admin_functions();

    # get csv
    $file = $_FILES[\'csv\'];

    $name = $file[\'name\'];
    $dir = $file[\'tmp_name\'];

    # rm spaces, replace with _
    $name = str_replace(\' \', \'_\', $name);

    $file_location = $_SERVER[\'DOCUMENT_ROOT\']. \'/wp-content/plugins/vendor-module/uploads/import/\'. $name;

    # if successfully moved, carry on, else return
    $moved = ($file[\'error\'] == 0 ? true : false);

    $error = false;

    if (!$moved) {
        echo \'Error! CSV file may be incorrectly formatted or there was an issue in reading the file. Please try again.\';
    } else {
        move_uploaded_file($dir, $file_location);

        $id = $_POST[\'val\'];
        $type = $_POST[\'type\'];

        $table = ($type == 1 ? \'vendor_module_type_one\' : \'vendor_module_type_two\');

        $handle = fopen($file_location, \'r\');

        $parts = array();
        $components = array();

        $i = 0;

        while (($data = fgetcsv($handle, 1000, \',\')) !== false)
        {
            if (is_array($data)) {
                if (empty($data[0])) {
                    echo \'Error! Reference can\\\'t be empty. Please ensure all rows are using a ref no.\';
                    $error = true;

                    continue;
                }

                # get data
                $get_for_sql = \'SELECT `id` FROM `\'. $wpdb->prefix. $table .\'` WHERE `ref` = %s\';
                $get_for_res = $wpdb->get_results($wpdb->prepare($get_for_sql, array($data[0])));

                if (count($get_for_res) <= 0) {
                    echo \'Error! Reference has no match. Please ensure the CSV is using the correct ref no.\';
                    $error = true;

                    exit();
                }

                $get_for_id = $get_for_res[0]->id;

                # create data arrays
                $parts[$i][\'name\'] = $data[1];
                $parts[$i][\'ref\'] = $data[2];
                $parts[$i][\'for\'] = $get_for_id;

                $components[$i][\'part_ref\'] = $data[2];
                $components[$i][\'component_ref\'] = $data[3];
                $components[$i][\'sku\'] = $data[4];
                $components[$i][\'desc\'] = utf8_decode($data[5]);
                $components[$i][\'req\'] = $data[6];
                $components[$i][\'price\'] = $data[7];

                unset($get_for_id);
                unset($get_for_res);
                unset($get_for_sql);

                $i++;
            }
        }

        fclose($handle);
        unlink($file_location);

        # get unique parts only
        $parts = array_unique($parts, SORT_REGULAR);

        # check to see if part already exists, if so delete
        $search_field = ($type == 1 ? \'id_field_one\' : \'id_field_two\');

        $check_sql = \'SELECT `id` FROM `\'. $wpdb->prefix .\'vendor_module_parts` WHERE `\'. $search_field .\'` = %d\';
        $delete_parts_sql = \'DELETE FROM `\'. $wpdb->prefix .\'vendor_module_parts` WHERE `\'. $search_field .\'` = %d\';
        $delete_components_sql = \'DELETE FROM `\'. $wpdb->prefix .\'vendor_module_components` WHERE `part_id` = %d\';

        $check_res = $wpdb->get_results($wpdb->prepare($check_sql, array($id)));

        if ($check_res) {
            $wpdb->query($wpdb->prepare($delete_parts_sql, array($id)));
        }

        $part_ids = $admin_functions->insert_parts($parts, $type);

        unset($parts);
        unset($delete_parts_sql);
        unset($search_field);
        unset($check_sql);
        unset($check_res);
        unset($i);

        # should never be empty, but just as a precaution ...
        if (!empty($part_ids)) {
            foreach ($components as $key => $component)
            {
                $components[$key][\'part_id\'] = $part_ids[$component[\'part_ref\']];
            }

            # rm components from assoc part id
            foreach ($part_ids as $id)
            {
                $wpdb->query($wpdb->prepare($delete_components_sql, array($id)));
            }

            # insert components
            $admin_functions->insert_components($components);
        } else {
            echo \'Error!\';
        }

        echo (!$error ? \'Success! File Successfully Imported.\' : \'There be something wrong with the import. Please try again.\');
    }
它是通过按下按钮触发的,并使用AJAX来处理它等。

我不知道为什么会发生内存泄漏,或者为什么WordPress没有提供更有用的错误消息。我不调用该函数。。所以我猜这是WordPress在后台运行时所做的事情。

我的信息:

PHP 7.2.10 Apache 2.4 Linux Mint 19

在我的服务器上也会发生:

PHP 7.1.25 Apache 2.4 CentOS 7.6.1810

WordPress运行版本:4.9.8

2 个回复
最合适的回答,由SO网友:kero 整理而成

我同意你的看法Tom\'s comment, WP-CLI命令可能更好。其优点是该命令直接从php在服务器上运行(通常没有最长执行时间,加载不同的php.ini等),并且不需要涉及Web服务器。

如果不可能,下一个最好的方法可能是create a custom REST endpoint. WordPress有一个类WP_REST_Controller, 通常我写的类extend 从这里开始工作。为了简单起见,下面的示例没有使用继承,但我尽量使用相同的行话。

1. Register new route

通过注册新/自定义路线register_rest_route() 就像这样

$version = 1;
$namespace = sprintf(\'acme/v%u\', $version);
$base = \'/import\';

\\register_rest_route(
    $namespace,
    $base,
    [
        [
            \'methods\' => \\WP_REST_Server::CREATABLE,
                         // equals [\'POST\',\'PUT\',\'PATCH\']

            \'callback\' => [$this, \'import_csv\'],
            \'permission_callback\' => [$this, \'get_import_permissions_check\'],
            \'args\' => [],
            // used for OPTIONS calls, left out for simplicity\'s sake
        ],
    ]
);
这将创建一条新路线,您可以通过

http://www.example.com/wp-json/acme/v1/import/
   default REST start-^       ^       ^
       namespace with version-|       |-base

2. Define permissions check

也许你需要身份验证?使用nonces?

public function get_import_permissions_check($request)
{
    //TODO: implement
    return true;
}

3. Create your actual endpoint callback

之前定义的方法通过WP_REST_Request 对象,使用它访问请求正文等。为了保持一致,通常最好返回WP_REST_Response 而不是自定义打印JSON或类似内容。

public function import_csv($request)
{
    $data = [];
    // do stuff
    return new \\WP_REST_Response($data, 200);
}
如果您以OOP风格完成所有这些,您将获得以下类

class Import_CSV
{

    /**
     * register routes for this controller
     */
    public function register_routes()
    {
        $version = 1;
        $namespace = sprintf(\'acme/v%u\', $version);
        $base = \'/import\';

        \\register_rest_route(
            $namespace,
            $base,
            [
                [
                    \'methods\' => \\WP_REST_Server::CREATABLE,
                    \'callback\' => [$this, \'import_csv\'],
                    \'permission_callback\' => [$this, \'get_import_permissions_check\'],
                    \'args\' => [],
                ],
            ]
        );
    }

    /**
     * endpoint for POST|PUT|PATCH /acme/v1/import
     */
    public function import_csv($request)
    {
        $data = [];
        // do stuff
        return new \\WP_REST_Response($data, 200);
    }

    /**
     * check if user is permitted to access the import route
     */
    public function get_import_permissions_check($request)
    {
        //TODO: implement
        return true;
    }

}
但是。。还是404?是的,遗憾的是,简单地定义类是行不通的(默认情况下没有自动加载:(),所以我们需要运行register_routes() 像这样(在插件文件中)

require_once \'Import_CSV.php\';
add_action(\'rest_api_init\', function(){
    $import_csv = new \\Import_CSV;
    $import_csv->register_routes();
});

SO网友:Tim Hallman

在函数文件中执行以下操作:

add_action(\'wp_ajax_import_parts_abc\', \'import_parts_abc\');
function import_parts_abc() {
    include_once(\'/admin/inc/scripts/import-parts.php\');
    exit();

}
然后在js文件中,如下所示:

jQuery(document).ready(function() {
     jQuery(\'.my-button\').click(function() {
jQuery.ajax({  
            type: \'GET\',  
            url: ajaxurl,  
            data: { 
                action : \'import_parts_abc\'
            },  
            success: function(textStatus){
                alert(\'processing complete\')
            },
            error: function(MLHttpRequest, textStatus, errorThrown){  
                console.log(errorThrown);  
            }  
      }); 
});

});