Comment streamer la création d'un fichier JSON?

J'essaye de créer un fichier JSON à partir d'un grand dump d'une requête de base de données, et fonctionne quand je fixe une limite à 100000 lignes étant retournées, mais quand je veux que toutes les lignes soient retournées, il va juste à une erreur 502 (la demande de page a été annulée parce qu'il a fallu trop de temps pour remplir). Vous vous demandez s'il y a un moyen de rationaliser la création d'un fichier JSON en bits en utilisant php, ou s'il y a une bibliothèque là-bas qui me permettra de construire le fichier json en parties?

essentiellement am l'exécution d'un .php fichier ICI pour tenter d'obtenir toutes les commandes dans le format json de woocommerce, depuis le plugin que j'ai acheté "CSV Import Suite" ne fonctionne pas lors de l'importation des commandes, il reste juste dans la file d'attente.

donc, j'ai décidé d'essayer d'exporter toutes les commandes moi-même, mais continuer à frapper une page D'erreur 502 et il ne crée jamais le .fichier json, donc pense j'ai besoin d'un moyen de diffuser ce en quelque sorte. Toute aide serait appréciée...

ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
error_reporting(E_ALL);
ob_implicit_flush(TRUE);
ob_end_flush();

global $wpdb, $root_dir;

if (!defined('ABSPATH'))
    $root_dir = dirname(__FILE__) . '/';
else
    $root_dir = ABSPATH;


$download = isset($_GET['download']);

// Allows us to use WP functions in a .php file without 404 headers!
require_once($root_dir . 'wp-config.php');
$wp->init();
$wp->parse_request();
$wp->query_posts();
$wp->register_globals();

if (empty($download))
    $wp->send_headers();

// exclude
$exclude_post_statuses = array('trash', 'wc-refunded', 'wc_cancelled');


$start_date = !empty($_GET['start_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['start_date']) : '';
$end_date = !empty($_GET['end_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['end_date']) : '';


$order_db = array(
    'columns' => array(
        'p' => array('ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', 'post_mime_type', 'comment_count'),
        'pm' => array('meta_id', 'post_id', 'meta_key', 'meta_value'),
        'oi' => array('order_item_id', 'order_item_name', 'order_item_type', 'order_id'),
        'oim' => array('meta_id', 'order_item_id', 'meta_key', 'meta_value')
    )
);

$select_data = '';
$total_columns = count($order_db['columns']);
$i = 1;

foreach($order_db['columns'] as $column_key => $columns)
{
    $select_data .= implode(', ', array_map(
        function ($v, $k) { return $k . '.' . $v . ' AS ' . $k . '_' . $v; },
        $columns,
        array_fill(0, count($columns), $column_key)
    ));

    if ($i < $total_columns)
        $select_data .= ', ';

    $i++;
}

// HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
$orders_query = $wpdb->get_results('
    SELECT ' . $select_data . '
    FROM ' . $wpdb->posts . ' AS p
    INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
    WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
    ORDER BY p.ID ASC', ARRAY_A);

$json = array();

if (!empty($orders_query))
{
    foreach($orders_query as $order_query)
    {
        if (!isset($json[$order_query['p_post_type']], $json[$order_query['p_post_type']][$order_query['p_post_name']]))
            $json[$order_query['p_post_type']][$order_query['p_post_name']] = array(
                'posts' => array(),
                'postmeta' => array(),
                'woocommerce_order_items' => array(),
                'woocommerce_order_itemmeta' => array()
            );

        if (!empty($order_query['p_ID']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['posts'][$order_query['p_ID']] = array_filter($order_query, function($k) {
                $is_p = strpos($k, 'p_');
                return $is_p !== FALSE && empty($is_p);
            }, ARRAY_FILTER_USE_KEY);

        if (!empty($order_query['pm_meta_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['postmeta'][$order_query['pm_meta_id']] = array_filter($order_query, function($k) {
                $is_pm = strpos($k, 'pm_');
                return $is_pm !== FALSE && empty($is_pm);
            }, ARRAY_FILTER_USE_KEY);

        if (!empty($order_query['oi_order_item_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_items'][$order_query['oi_order_item_id']] = array_filter($order_query, function($k) {
                $is_io = strpos($k, 'oi_');
                return $is_io !== FALSE && empty($is_io);
            }, ARRAY_FILTER_USE_KEY);


        if (!empty($order_query['oim_meta_id']))
            $json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_itemmeta'][$order_query['oim_meta_id']] = array_filter($order_query, function($k) {
                $is_oim = strpos($k, 'oim_');
                return $is_oim !== FALSE && empty($is_oim);
            }, ARRAY_FILTER_USE_KEY);
    }
}

// Downloading or viewing?
if (!empty($download))
{
    // Outputs json in a textarea for you to copy and paste into a .json file for import...

    if (!empty($json))
    {
        $filename = uniqid('orders_') . '.json';

        $fp = fopen($filename, 'w');
        fwrite($fp, json_encode($json));
        fclose($fp);


        $size   = filesize($root_dir . '/' . $filename);
        header('Content-Description: File Transfer');
        header('Content-Type: application/octet-stream');
        header("Content-Disposition: attachment; filename="" . $filename . """); 
        header('Content-Transfer-Encoding: binary');
        header('Connection: Keep-Alive');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');
        header('Content-Length: ' . $size);

        readfile($root_dir . '/' . $filename);
    }

}
else
{
    // Outputs json in a textarea for you to copy and paste into a .json file for import...
    if (!empty($json))
        echo '<textarea cols="200" rows="50">', json_encode($json), '</textarea>';
}

le fichier JSON créé pourrait être un bien plus de 500 Mo, et peut-être même jusqu'à 1 Gig de données. Donc, je crois que PHP manque de mémoire ici, et doit être traité petit à petit, soit en arrière-plan, soit complètement, sans atteindre la limite de mémoire php. Je crois que la limite de mémoire est fixée à 1024 Mo, ce qui est assez élevé, mais pas assez élevé et tbh, pour ce que je fais, Je ne pense pas que nous pouvons jamais avoir assez de mémoire pour effectuer l'opération telle quelle. Quelque chose doit changer dans la façon dont je traite le json et / ou télécharger il. Et je ne veux pas créer plusieurs fichiers json, s'il vous plaît seulement 1 fichier JSON.

17
demandé sur Solomon Closson 2018-01-31 09:29:22

6 réponses

je pense qu'il pourrait y avoir quelques problèmes. Tout d'abord, je vous suggère de faire quelques recherches.

    // HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
    echo 'Start Time: '. date("Y-m-d H:i:s");
    echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';

    $orders_query = $wpdb->get_results('
        SELECT ' . $select_data . '
        FROM ' . $wpdb->posts . ' AS p
        INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
        LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
        LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
        WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
        ORDER BY p.ID ASC', ARRAY_A);

    echo 'End Time: '. date("Y-m-d H:i:s");
    echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';
    die('Finished');

    $json = array();

ce qui précède vous aidera à savoir combien de mémoire est utilisée, jusqu'à ce point. S'il échoue avant d'imprimer "fini", nous savons que ce n'est pas un problème json. Si le script fonctionne bien, nous pouvons d'abord créer un fichier csv plutôt json. Puisque vous exécutez une requête select, (à ce point) il n'est pas nécessaire que ce soit un fichier JSON imbriqué dont vous avez besoin. Une structure plate peut être obtenue par je crée juste un fichier CSV.

$csvFile = uniqid('orders') . '.csv';
$fp = fopen($csvFile, 'w');
if (!empty($orders_query))
{
    $firstRow = true;
    foreach($orders_query as $order_query)
    {
        if(true === $firstRow) {
            $keys = array_keys($order_query);
            fputcsv($fp, $order_query);
            $firstRow = false;
        }

        fputcsv($fp, $order_query);
    }
}
fclose($fp);

si ce qui précède fonctionne bien, vous avez au moins un fichier csv avec lequel travailler.

à ce stade, Je ne suis pas sûr de la complexité de votre structure de données imbriquée. Pour l'e.g combien de valeurs distinctes existent pour 'p_post_type" et "p_post_name' que vous rencontrez. Vous devrez peut-être analyser le fichier csv et créer plusieurs fichiers json pour chaque ['p_post_type']['p_post_name']['messages'], ['p_post_type']['p_post_name']['messages'], ['p_post_type']['p_post_name']['woocommerce_order_items'] et ['p_post_type']['p_post_name']['woocommerce_order_itemmeta'].

Si le nombre de fichiers que vous pouvez écrire un script pour les fusionner automatiquement ou de les faire manuellement. Si vous avez trop d'éléments imbriqués, le nombre de fichiers json qui pourrait être créé peut-être un beaucoup et peut-être difficile de les fusionner et pourrait ne pas être une option réalisable.

Si le nombre de fichiers json sont beaucoup, je voudrais savoir quel est le but d'avoir un énorme seul fichier json. Si l'exportation est une question d'importation peut être un problème, surtout ingestion d'un énorme fichier json dans la mémoire. Je crois que si le but de créer le fichier json est de l'importer sous une forme ou une autre, à un certain stade dans le futur, je pense que vous pourriez avoir à examiner l'option d'avoir juste un fichier csv à la place, que vous utilisez pour filtrer tout ce qui est nécessaire à ce moment-là.

j'espère que cette aide.

autres MISE à jour

Il me semble que l' $wpdb->get_results utilise mysqli_query / mysql_query (selon votre configuration) pour récupérer les résultats. Voir wordpress requête docs. Ce n'est pas une façon efficace de récupérer les données de cette façon. Je crois que vous pourriez être en train d'échouer à ce point ( $wpdb->get_results). Je vous suggérerais d'exécuter la requête sans utiliser $wpdb. Il y a un concept de requête libre chaque fois que la récupération de grandes données est nécessaire, qui a un impact très faible sur la mémoire. Plus d'informations peuvent être trouvées ici mysql unbuffering.

même si vous dépassez ce point, vous rencontrerez quand même des problèmes de mémoire, en raison de la façon dont vous stockez tout dans $json variable qui vous ronge la mémoire. $json est un tableau et il serait intéressant de savoir comment fonctionne PHP array. Les tableaux PHP sont dynamiques et n'allouent pas de mémoire supplémentaire chaque fois qu'un nouvel élément est ajouté, car cela serait extrêmement lent. Il augmente plutôt la taille du tableau à la puissance de deux, ce qui signifie que chaque fois que la limite est épuisée il augmente la limite du tableau à deux fois sa limite actuelle et dans le processus tente d'augmenter la mémoire à deux fois la limite. Cela a été moins un problème avec PHP 7, car ils ont apporté des changements majeurs au noyau php. Donc, si vous avez 2 Go de données qui pourraient être nécessaires pour être stockés dans $json, le script peut facilement allouer n'importe où entre 3-4 Go de mémoire, en fonction de quand il atteint la limite. Plus de détails peuvent être trouvés ici tableau php et Comment fonctionne la mémoire PHP

si vous tenez compte des frais généraux $ orders_query qui est un tableau combiné avec un ciel de $json il est assez important en raison de la façon dont le tableau PHP fonctionne.

vous pouvez également essayer de créer une autre base de données B. Ainsi, pendant que vous lisez à partir de la base de données A, vous commencez simultanément à écrire des données à la base de données B. à la fin, vous avez la base de données B avec toutes les données qu'elle contient avec la puissance de MySQL. Vous pouvez également pousser les mêmes données dans un MongoDB qui serait éclair rapide et pourrait vous aider avec le JSON nidification vous êtes après. MongoDB est destiné à travailler vraiment efficacement avec de grands ensembles de données.

JSON STREAMING SOLUTION

tout d'abord, je voudrais dire que la diffusion en continu est un processus séquentiel/linéaire. En tant que tel, il est n'ont pas la mémoire de ce qui a été ajouté avant ce moment ou ce sera ajoutée après ce point de temps. Il fonctionne en petits morceaux et c'est la raison pour laquelle il est si efficace de mémoire. Alors, quand vous écrivez ou lisez réellement, la responsabilité repose sur le script, qui maintient un ordre spécifique, ce qui est en quelque sorte de dire que vous écrivez/lisez votre propre json, car le streaming ne comprend que le texte et n'a aucune idée de ce qu'est json et ne s'embêtera pas à écrire/lire un texte correct.

j'ai trouvé une bibliothèque sur github https://github.com/skolodyazhnyy/json-stream ce qui vous aiderait à réaliser ce que vous voulez. J'ai expérimenté avec le code et Je vois que ça va marcher pour toi avec quelques modifications dans ton code.

je vais vous écrire un pseudo-code.

//order is important in this query as streaming would require to maintain a proper order.
$query1 = select distinct p_post_type from ...YOUR QUERY... order by p_post_type;
$result1 = based on $query1; 

$filename = 'data.json';
$fh = fopen($filename, "w");
$writer = new Writer($fh);
$writer->enter(Writer::TYPE_OBJECT);  

foreach($result1 as $fields1) {
    $posttype = $fields1['p_post_type'];
    $writer->enter($posttype, Writer::TYPE_ARRAY); 

    $query2 = select distinct p_post_name from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype order by p_post_type,p_post_name;
    $result2 = based on $query2;

    foreach($result2 as $fields2) {
        $postname = $fields1['p_post_name'];
        $writer->enter($postname, Writer::TYPE_ARRAY); 

        $query3 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where p_ID is not null order by p_ID;
        $result3 = based on $query3;
        foreach($result2 as $field3) {
            $writer->enter('posts', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field3);
        }
        $writer->leave(); 

        $query4 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where pm_meta_id is not null order by pm_meta_id;
        $result4 = based on $query4;
        foreach($result4 as $field4) {
            $writer->enter('postmeta', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field4);
        }
       $writer->leave(); 

        $query5 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oi_order_item_id is not null order by oi_order_item_id;
        $result5 = based on $query5;
        foreach($result5 as $field5) {
            $writer->enter('woocommerce_order_items', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field5);
        }
        $writer->leave(); 

        $query6 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oim_meta_id is not null order by oim_meta_id;
        $result6 = based on $query6;
        foreach($result6 as $field6) {
            $writer->enter('woocommerce_order_itemmeta', Writer::TYPE_ARRAY); 
            // write an array item
            $writer->write(null, $field5);
        }
        $writer->leave(); 

    }
$writer->leave(); 
fclose($fh);

vous pourriez devoir commencer à limiter vos requêtes à 10 quelque chose jusqu'à ce que vous obteniez le droit. Puisque le code ci-dessus pourrait ne pas fonctionner comme il est. Vous devriez être en mesure de lire le code de la même façon que la même bibliothèque a une classe de lecteur à aider. J'ai testé le lecteur et l'écrivain et ils semblent bien fonctionner.

8
répondu KamalSoni 2018-02-09 03:04:02

Créer le fichier

le problème avec votre code est que vous essayez de faire rentrer des données entières dans la mémoire, ce qui finira par échouer dès que votre base de données sera assez grande. Pour surmonter cela, vous devez récupérer les données par lots.

Nous allons générer la requête plusieurs fois, donc je extraites votre requête dans une fonction. J'ai sauté passer les paramètres requis (ou les rendre globaux si vous voulez) pour la brièveté donc vous devez obtenir ceci pour travailler par m'.

function generate_query($select, $limit = null, $offset = null) {
    $query = 'SELECT ' . $select . '
    FROM ' . $wpdb->posts . ' AS p
    INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
    LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
    WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
    ORDER BY p.ID ASC';

    if ($limit && $offset) {
        $query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
    }

    return $query;
}

maintenant nous allons obtenir les résultats de la base de données en lots, nous définissons le nombre de lots qui est le nombre d'enregistrements par itération que nous allons charger dans la mémoire. Vous pouvez plus tard jouer avec cette valeur pour trouver celle qui sera assez rapide et ne fera pas planter PHP. Gardez à l'esprit que nous voulons réduire le plus possible le nombre de requêtes de base de données:

define('BATCH_COUNT', 500);

avant de créer la boucle nous avons besoin de savoir combien d'itérations (appels de base de données) nous allons faire, donc nous avons besoin du nombre total de commandes. Ayant ceci et le nombre de lots, nous pouvons calculer cette valeur facilement:

$orders_count = $wpdb->get_col(generate_query('COUNT(*)'));
$iteration_count = ceil($orders_count / BATCH_COUNT);

en conséquence, nous aimerions avoir une chaîne JSON énorme à l'intérieur du fichier de résultat. Comme à chaque itération nous aurons un JSON séparé contenant un tableau d'objets, nous allons simplement supprimer le [ et ] de chaque côté de la chaîne JSON et mettez cette chaîne dans le fichier.

code Final:

define('FILE', 'dump.json');
file_put_contents(FILE, '[');

for ($i = 0; $i < $iteration_count; $i++) {
    $offset = $i * BATCH_COUNT;

    $result = $wpdb->get_results(
        generate_query($select_data, BATCH_COUNT, $offset),
        ARRAY_A
    );

    // do additional work here, add missing arrays etc.
    // ...

    // I assume here the $result is a valid array ready for
    // creating JSON from it
    // we append the result file with partial JSON

    file_put_contents(FILE, trim(json_encode($result), '[]'), FILE_APPEND);
}

file_put_contents(FILE, ']', FILE_APPEND);

félicitations, vous avez juste créé votre premier dump JSON énorme;) vous devriez exécuter ce script dans la ligne de commande afin qu'il puisse obtenir aussi longtemps qu'il a besoin, il n'y a pas besoin de modifier la limite de mémoire à partir de maintenant, parce que nous ne allons jamais frapper la limite espérons.

envoyer le fichier

streamer de gros fichiers avec PHP est facile et a déjà reçu une réponse sur tant de fois. Cependant, personnellement, je ne vous recommande pas de faire quelque chose de long en PHP, parce que ça craint comme un long processus d'exécution, que ce soit en ligne de commande ou en tant que serveur de fichiers.

je suppose que vous utilisez Apache. Vous devriez envisager d'utiliser SendFile et laissez Apache faire le travail pour vous. Cette méthode est beaucoup plus efficace quand il s'agit de gros fichiers. Cette méthode est très facile, tout ce que vous devez faire est de passer le chemin d'accès au fichier dans l'en-tête:

header('X-Sendfile: ' . $path_to_the_file);

si vous utilisez Nginx il y a XSendFile soutien.

cette méthode ne ne pas utiliser beaucoup de mémoire, ne bloque pas le processus PHP. Le fichier n'a pas besoin d'être accessible dans le webroot aussi. J'utilise XSendFile tout le temps pour servir des vidéos 4K aux utilisateurs authentifiés.

3
répondu emix 2018-02-09 09:49:00

tout d'abord, vous devriez vous poser une question: Est-ce que j'ai besoin de faire le vidage de la base de données moi-même?

Si non, alors vous pouvez simplement utiliser un service qui va faire le travail pour vous. Mysqldump-php devrait être capable de faire le travail.

alors vous pouvez simplement:

include_once(dirname(__FILE__) . '/mysqldump-php-2.0.0/src/Ifsnop/Mysqldump/Mysqldump.php');
$dump = new Ifsnop\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password');
$dump->start('storage/work/dump.sql');

Cela devrait créer .sql fichier. Toutefois, vous souhaitiez json fichier. Qui ne devrait pas être un problème. Cet outil fera le reste du travail: http://www.csvjson.com/sql2json

vous pouvez aussi trouver le code source de sql2json sur github: https://github.com/martindrapeau/csvjson-app

0
répondu Michal 2018-02-02 19:39:44

je crois que vous cherchez peut-être Generators http://php.net/manual/en/language.generators.overview.php https://scotch.io/tutorials/understanding-php-generators

au lieu de créer cet énorme $json array, vous itérer sur chaque $order_query et effectuer des opérations sur chaque itération, annulant la nécessité de le stocker en mémoire.

0
répondu Angelo Manos 2018-02-07 08:38:36

Votre problème est que vous obtenez un grand jeu de résultats à partir de votre requête, qui est lourde puisque vous avez 3 joins.

Vous pouvez définir un limite et l'utilisation de décalage pour obtenir les données dans morceaux et ensuite sortir votre json en pièces détachées. Le problème principal est d'obtenir les données json en mémoire et ensuite d'y accéder à la sortie dans les pièces.

pour ce dernier cache ou une base de données nosql pourrait être utilisé. Ma solution utilisera cache et en particulier, memcache:

class Cache {

    private $cache;

    public function __construct($cache)
    {
        $this->cache = $cache;
    }

    public function addPostName($postName)
    {
        $this->addKeyToJsonObject('postNames', $postName);
    }

    public function addKeyToJsonObject($rootName, $key)
    {
        $childNames = $this->cache->get($rootName);
        if($childNames === false) {
            $this->cache->set($rootName, [$key]);
        }
        else {
            $childNamesList = $childNames;
            // not found
            if(array_search($key, $childNamesList) === false) {
                $childNamesList[] = $key;
                $this->cache->set($rootName, $childNamesList);
            }
        }
    }

    public function getPostNames()
    {
        return $this->cache->get('postNames');
    }
    public function set($key, $value) {
        $this->cache->add($key, $value);
    }

    public function addPostIdsByNameAndType($postName, $type, $pid)
    {
        $this->addKeyToJsonObject($postName . '-' . $type, $pid);
    }

    public function getPostIdsByNameAndType($postName, $type)
    {
        return $this->cache->get($postName . '-' . $type);
    }

    public function addPostValueByNameTypeAndId($postName, $type, $pid, $value)
    {
        $this->cache->set($postName . '-' . $type . '-' . $pid, $value);
    }

    public function getPostValueByNameTypeAndId($postName, $type, $pid)
    {
        return $this->cache->get($postName . '-' . $type . '-' . $pid);
    }
}

puis:

$memcache = new Memcache();
$memcache->connect('127.0.0.1', 11211) or die ("Could not connect");

$memcache->flush();

$cache = new Cache($memcache);

header('Content-disposition: attachment; filename=file.json');
header('Content-type: application/json');
echo '{"shop_order":{';

function getResultSet($wpdb, $offset = 1) {
    return $wpdb->get_results('
    SELECT ' . $select_data . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC LIMIT 1000 OFFSET ' . $offset, ARRAY_A);

}
$offset = 1;

$orders_query = getResultSet($wpdb, 1);
while(!empty($orders_query)) {
    cacheRowData($cache, $orders_query);
    $offset = $offset + 1000;
    $orders_query = getResultSet($wpdb, $offset);
}

outputRowData($cache);

function cacheRowData($cache, $orders_query)
{
    foreach($orders_query as $order_query) {

        if(empty($order_query)) { continue; }
        $cache->addPostName($order_query['p_post_name']);

        // posts
        if (!empty($order_query['p_ID'])) {
            $cache->addPostIdsByNameAndType($order_query['p_post_name'],'posts', $order_query['p_ID']);

            $value = array_filter($order_query, function($k) {
                $is_p = strpos($k, 'p_');
                return $is_p !== FALSE && empty($is_p);
            }, ARRAY_FILTER_USE_KEY);
            $cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'posts', $order_query['p_ID'], $value);
        }
        if (!empty($order_query['pm_meta_id'])) {
        $cache->addPostIdsByNameAndType($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id']);

        $value = array_filter($order_query, function($k) {
            $is_pm = strpos($k, 'pm_');
            return $is_pm !== FALSE && empty($is_pm);
        }, ARRAY_FILTER_USE_KEY);
        $cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id'], $value);
    }
        // here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"
    }
}

function outputRowData($cache)
{

    $cachedPostNames = $cache->getPostNames();
    $firstRow = true;

    foreach($cachedPostNames as $postName) {

        if(empty($postName)) { continue; }

        if($firstRow === false) {
            echo ',';
        }
        $firstRow = false;

        echo '"' . $postName . '":{';
        $postIds = $cache->getPostIdsByNameAndType($postName, 'posts');
        if(!$postIds) {
            $postIds = [];
        }

        // generate posts
        $postValues = [];
        foreach ($postIds as $postId) {
            $postValues[$postId] = $cache->getPostValueByNameTypeAndId($postName, 'posts', $postId);
        }

        $postMetaIds = $cache->getPostIdsByNameAndType($postName, 'postmeta');
        if(!$postMetaIds) {
            $postMetaIds = [];
        }
        $postMetaValues = [];
        foreach ($postMetaIds as $postMetaId) {
            $postMetaValues[$postMetaId] = $cache->getPostValueByNameTypeAndId($postName, 'postmeta', $postMetaId);
        }
        // here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"

        echo '"posts":' . json_encode($postValues) . ',';
        echo '"postmeta":' . json_encode($postMetaValues);
        echo '}';
        ob_flush();
        flush();   // flush the output to start the download
    }
}


echo '}}';
0
répondu Jannes Botis 2018-02-09 09:44:50

donc il y a beaucoup de choses dont vous avez besoin pour travailler à cette droite. Je vais tous les points que j'ai à l'esprit.

Résiliation par le Serveur

si vous utilisez Apache ou Nginx / PHP-FPM, tous les deux ont par défaut un délai pour l'url qui est touchée. Donc, même si vous avez utilisé

ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);

pour laisser le script tourner longtemps, mais Apache, Nginx, PHP-FPM ont tous un timeout qui ne permettra pas à votre script de fonctionner. Donc tu as besoin de réparer ça pour l'avoir. travailler. Vous n'avez jamais mentionné le serveur que vous avez utilisé. Mais un NGINX+PHP-FPM donnera certainement 502 avec la configuration par défaut.

Utilisation De La Mémoire

Même si vous avez utilisé

ini_set('memory_limit', '-1');

si vos besoins de mémoire sont élevés, PHP peut commencer à utiliser la pagination et votre code pourrait devenir lent.

PHP CLI ou PHP Web?

Je ne sais pas quelle est la fréquence d'exécution ici, mais si elle est faible, vous pouvez considérer vos données le script de dumping doit être exécuté par PHP-CLI au lieu de HTTP. Cela signifierait que vous exécuteriez un script PHP directement à travers le terminal pour décharger le JSON dans un fichier et ensuite utiliser une URL pour télécharger le fichier directement

en utilisant X-Sendfile ou X-Accel-Redirect

Si vous utilisez apache, vous pouvez envoyer un en-tête

header('X-Sendfile: /data/generated.json');

En cas de Nginx, vous pouvez envoyer un

header('X-Accel-Redirect: /data/generated.json');

vous ne le feriez que si vous aviez décidé d'exécuter le script comme web et pas comme CLI. Lorsque la génération du JSON est terminée, vous ne voulez pas que votre script lise le fichier et le serveur. Vous voulez juste que le webserver s'en occupe.

interrogation Débuffrée à la place de la requête WPDB

https://core.trac.wordpress.org/browser/tags/4.9/src/wp-includes/wp-db.php#L2480

par défaut la requête wpdb récupère toutes les données dans la mémoire. Mais vous pouvez interroger le DB vous-même en utilisant Unbuffered query, ce sera ne pas inonder la mémoire

Example #1 Unbuffered query example: mysqli

<?php
$mysqli  = new mysqli("localhost", "my_user", "my_password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);

if ($uresult) {
   while ($row = $uresult->fetch_assoc()) {
       echo $row['Name'] . PHP_EOL;
   }
}
$uresult->close();
?>
Example #2 Unbuffered query example: pdo_mysql

<?php
$pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);

$uresult = $pdo->query("SELECT Name FROM City");
if ($uresult) {
   while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
       echo $row['Name'] . PHP_EOL;
   }
}
?>
Example #3 Unbuffered query example: mysql

<?php
$conn = mysql_connect("localhost", "my_user", "my_pass");
$db   = mysql_select_db("world");

$uresult = mysql_unbuffered_query("SELECT Name FROM City");
if ($uresult) {
   while ($row = mysql_fetch_assoc($uresult)) {
       echo $row['Name'] . PHP_EOL;
   }
}
?>

https://secure.php.net/manual/en/mysqlinfo.concepts.buffering.php

PS: il pourrait y avoir quelques points supplémentaires que je manque en ce moment dans ma tête, serait mettre à jour cela bientôt

0
répondu Tarun Lalwani 2018-02-10 04:45:50