web-dev-qa-db-fra.com

Fuite de mémoire dans l'action du plugin

J'ai créé un plugin qui lit un fichier CSV et importe des données dans les tables correspondantes.

Cependant, l'action semble créer une erreur:

Erreur fatale: la taille de mémoire autorisée de 134217728 octets a été épuisée (tentative d'allocation de 65015808 octets) dans /var/www/proj/wp-includes/functions.php en ligne

ce qui m'a amené à ce code dans functions.php:

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

J'ai fait un Google et suis tombé sur deux solutions populaires, les deux ne semblant pas fonctionner.

Solution 1: désactivation de zlib - ceci est déjà désactivé.
Solution 2: remove_action('shutdown', 'wp_ob_end_flush_all', 1);

La solution 2 continue à générer des erreurs, mais pas de message, ce qui n’est pas vraiment idéal.

C'est le script qui cause l'erreur:

<?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.');
    }

il est déclenché par une pression sur un bouton et utilise AJAX pour le gérer, etc.

Je ne sais pas pourquoi une fuite de mémoire se produit ou pourquoi WordPress n'offre pas de messages d'erreur plus utiles. Je n’appelle pas cette fonction. Je suppose donc que c’est quelque chose que WordPress fait en arrière-plan lorsque les choses tournent.

Mon info:

PHP 7.2.10
Apache 2.4
Linux Mint 19

Cela se passe aussi sur mon serveur:

PHP 7.1.25
Apache 2.4
CentOS 7.6.1810

WordPress version en cours d'exécution: 4.9.8

1
treyBake

En fonction de l'objectif recherché, je partage l'avis de Tom , selon lequel une commande WP-CLI pourrait être meilleure. L'avantage est que la commande est exécutée à partir de php directement sur le serveur (généralement, elle n'a pas de délai d'exécution maximal, charge un fichier php.ini différent, etc.) et vous n'avez pas besoin d'impliquer le serveur Web.


Si cela n’est pas possible, la meilleure solution consiste probablement à créer un point de terminaison REST personnalisé . WordPress a une classe WP_REST_Controller , d'habitude j'écris des classes qui extend this et travaillent à partir de là. Pour des raisons de simplicité, l'exemple suivant n'utilise pas l'héritage, mais j'essaie de garder le même jargon.

1. Enregistrer un nouvel itinéraire

Les nouveaux itinéraires/itinéraires personnalisés sont enregistrés via register_rest_route() comme suit

$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
        ],
    ]
);

Cela créera un nouvel itinéraire que vous pourrez appeler via

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

2. Définir le contrôle des autorisations

Peut-être avez-vous besoin d'une authentification? Utiliser des nonces?

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

3. Créez votre rappel de point final réel

La méthode précédemment définie reçoit un objet WP_REST_Request , elle sert à accéder au corps de la demande, etc. Pour rester cohérent, il est généralement préférable de renvoyer un WP_REST_Response au lieu d’une impression personnalisée de JSON ou similaire.

public function import_csv($request)
{
    $data = [];
    // do stuff
    return new \WP_REST_Response($data, 200);
}

Si vous faites tout cela dans le style OOP, vous aurez la classe suivante

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

}

Mais .. encore 404? Oui, simplement définir la classe ne fonctionne malheureusement pas (pas de chargement automatique par défaut :(), nous devons donc exécuter register_routes() comme ceci (dans votre fichier de plugin)

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

Dans votre fichier de fonctions, faites quelque chose comme ça:

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

}

Ensuite, dans votre fichier js, quelque chose comme ceci:

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