web-dev-qa-db-ja.com

プラグインアクションでのメモリリーク

私は基本的にCSVファイルを読み込み、関連するテーブルにデータをインポートするプラグインを作りました。

しかし、このアクションはエラーを引き起こすようです。

致命的なエラー:134217728バイトの許容メモリサイズを使い果たしました(65015808バイトを割り当てようとしました) /var/www/proj/wp-includes/functions.php オンラインで

これは私をfunctions.phpのこのコードに導いた:

function wp_ob_end_flush_all() {
    $levels = ob_get_level();
    for ($i=0; $i<$levels; $i++)
        ob_end_flush();
}

私はGoogleをやったところ、2つの一般的な解決策を見つけましたが、どちらもうまくいかないようでした。

解決策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
アパッチ2.4
Linuxミント19

私のサーバーでも起こります:

PHP 7.1.25
アパッチ2.4
CentOS 7.6.1810

WordPressランニングバージョン:4.9.8

1
treyBake

正確に何を達成しようとしているかによりますが、WP-CLIコマンドの方が良いかもしれないという Tomのコメント に同意します。利点は、コマンドがphpから直接サーバー上で実行され(通常、最大実行時間がない、異なるphp.iniを読み込むなど)、Webサーバーを使用する必要がないことです。


それが不可能な場合、次の最も良い方法はおそらく カスタムRESTエンドポイントを作成することです 。 WordPressには WP_REST_Controller というクラスがあり、通常私はこれをextendするクラスを書いてそこから動作します。簡単にするために、次の例では継承を使用していませんが、私は同じ用語を使用します。

1.新しい経路を登録する

新規/カスタムルートは 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.パーミッションチェックを定義します

たぶんあなたは認証が必要ですか?ナンスを使用しますか?

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

3.実際のエンドポイントコールバックを作成します

以前に定義されたメソッドは WP_REST_Request オブジェクトを渡され、リクエストボディなどにアクセスするためにそれを使用します。一貫性を保つために、通常JSONのカスタム印刷の代わりに WP_REST_Response を返すのが最善です。

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();
});
1
kero

あなたの関数ファイルで、このようなことをしてください:

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);  
            }  
      }); 
});

});
0
Tim Hallman