Выделение памяти, исчерпаны байты PHP / LARAVEL - PullRequest
0 голосов
/ 08 октября 2019

Привет, ребята,

Я занимаюсь разработкой системы с использованием Laravel Excel / Maatwebsite . Я пытаюсь добиться того, чтобы пользователь вставил файл Excel в систему, система проверит несколько вещей и затем вставит данные в базу данных.

Вот примерсхемы базы данных:

Hadees:
h_id | h_english | r_id | h_last_raavi_id | b_id | s_id | k_id | h_status

Raavi:
r_id | r_english | r_status

Baab:
b_id | b_english | s_id | b_status

Section:
s_id | s_english | k_id | s_status

Kitaab:
k_id | k_english | k_status

Мой контроллер:

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Section;
use App\Models\Raavi;
use App\Imports\HadeesImport;
use Excel;

class ImportHadeesController extends Controller{
    /**
     * Show the application import-hadees page.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(){
        $section = Section::where(['s_status' => 1])->get();

        return view('admin/import-hadees', compact('section'));
    }

    /**
     * This method uses the Excel facade to prep the excel file 
     * and extract data from it and uses App\Imports\HadeesImport 
     * class to insert each row in the database schema accordingly.
     * 
     * @param Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function importHadees(Request $request){
        $raavi = Raavi::where(['r_status' => 1])->get();

        if($request->file('hadees_sheet')) {
        } else {
           return response()->json(['status'=>'error', 'msg'=> 'No file present!']);
        }

        $temp = $request->file('hadees_sheet')->store('temp'); 
        $path=storage_path('app').'/'.$temp;

        $hadees = Excel::import(new HadeesImport($request->s_id, compact('raavi')), $path);

        if($hadees){
            return response()->json(['status'=>'success', 'msg'=> 'Successfully imported all the data from the file to the database!']);
        } else{
            return response()->json(['status'=>'error', 'msg'=> 'Unable to import data from the file to the database!']);
        }
    }
}

HadeesImport Класс:

use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use App\Models\Section;
use App\Models\Baab;
use App\Models\Hadees;
use App\Models\Raavi;

class HadeesImport implements ToCollection, WithHeadingRow{
    /**
     * Global variable for section_id.
     */
    public $s_id;

    /**
     * Global variable for raavi's data.
     */
    public $raavi;

    /**
     * Construction function.
     * 
     * @param int $id
     */
    function __construct($id, $arr) {
        $this->s_id = $id;
        $this->raavi = $arr;
    }

    /**
    * This method is responsible for inserting the excel
    * sheet's rows data to the database schema.
    * 
    * @param Collection $row
    */
    public function collection(Collection $rows){
        $baab = Baab::where(['s_id' => $this->s_id])->get();
        $hissa = Section::where(['s_id' => $this->s_id])->first();
        $kitaab = $hissa->k_id;
        $first_raavi = 0;
        $last_raavi = 0;
        $baab_id = 0;
        $data = array();

        foreach ($rows as $row){
            if($row['hadees_arabic'] != "" && $row['hadees_urdu'] != ""){
                $baab_id = $this->baabCheck($baab, $row);

                foreach($this->raavi['raavi'] as $rav){
                    if(trim($rav->r_english) == trim($row['first_raavi_english'])){ 
                        $first_raavi = $rav->r_id; 
                    } else{
                        $first_raavi = 0;
                    }

                    $last_raavi = (trim($rav->r_english) == trim($row['last_raavi_english']))? $rav->r_id : 0;
                }

                if($first_raavi == 0){
                    $raavi = Raavi::create([
                        'r_arabic' => trim($row['first_raavi_urdu']),
                        'r_urdu' => trim($row['first_raavi_urdu']),
                        'r_english' => trim($row['first_raavi_english']),
                        'r_nickname' => trim($row['raavi_other_names']),
                        'r_status' => 1,
                    ]);

                    $first_raavi = $raavi->r_id;
                }

                if($last_raavi == 0){
                    $raavi = Raavi::create([
                        'r_arabic' => trim($row['last_raavi_urdu']),
                        'r_urdu' => trim($row['last_raavi_urdu']),
                        'r_english' => trim($row['last_raavi_english']),
                        'r_nickname' => 'n/a',
                        'r_status' => 1,
                    ]);

                    $last_raavi = $raavi->r_id;
                }

                $data = array([
                    'h_arabic' => trim($row['hadees_arabic']),
                    'h_urdu' => trim($row['hadees_urdu']),
                    'h_english' => trim($row['hadees_english']),
                    'h_roman_keywords' => trim($row['roman_keywords']),
                    'h_number' => trim($row['hadees_number']),
                    'r_id' => $first_raavi,
                    'h_last_raavi_id' => $last_raavi,
                    'b_id' => $baab_id,
                    's_id' => $this->s_id,
                    'k_id' => $kitaab,
                    'h_status' => 1
                ]);
            }
        }

        $hadees = Hadees::create($data);
    }

    /**
    * Checks if the baab exists in the database or not.
    * 
    * @param Collection $baab
    * @param Object $row
    * @return int - baad_id or 0
    */
    public function baabCheck($baab, $row){
        foreach($baab as $b){
            if(trim($b->b_arabic) == trim($row['baab_arabic']) || trim($b->b_urdu) == trim($row['baab_urdu']) || trim($b->b_english) == trim($row['baab_english'])){
                return $b->b_id;
            } else{
                return 0;
            }
        }
    }
}

Все работало нормально, когда данных было меньше. Теперь у меня есть 1400 + строк в таблице Raavi и 10000 + строк в таблице Baab . Теперь всякий раз, когда я пытаюсь импортировать лист в систему, выдается следующее сообщение:

Разрешен допустимый объем памяти 268435456 байт (попытался выделить 37748736 байт).

Я думаю, что это из-за столь длинного цикла foreach () . Любая помощь будет принята с благодарностью. Если у вас есть какие-либо предложения по поводу плохого кодирования или плохой логики, пожалуйста, дайте мне знать. Я застрял в этом вопросе в течение почти двух дней. Заранее спасибо.

Ps : ошибка одинакова на локальном хосте и на хостинге, только разница в байтах. По-моему, это связано с другой настройкой memory_limit.

Ответы [ 4 ]

1 голос
/ 08 октября 2019

Во всех опубликованных решениях упоминается увеличение лимита памяти для PHP.

Это не работает так. Вы не можете просто бросить больше оперативной памяти при проблеме. Что если на вашем сервере 2 ГБ ОЗУ и вы загружаете файл, который со всеми созданными в нем массивами может использовать более 2 ГБ ОЗУ? Что дальше? Не говоря уже о риске нехватки памяти на сервере и уничтожения других процессов. Например, если на сервере запущены общий доступ к PHP и MySQL, а PHP заставляет сервер исчерпать память, OOM Killer сработает, и может убить процесс MySQL.

Решение вашей проблемы - обработать этот файл кусками. Например, в Laravel 6 появился новый тип коллекции: Lazy Collections . Они могут помочь вам ускорить процесс. Ваш код, возможно, придется изменить, чтобы использовать обработку чанка, но imho, это единственный способ решить эту проблему.

Я бы также запустил это в очереди, а не по запросу пользователя.

Я также знаю, что используемый вами пакет поддерживает chunking для чтения и batching для вставки.

1 голос
/ 08 октября 2019

Вы можете установить memory_limit для определенных файлов / страниц / скриптов так, чтобы он был больше, чем устанавливает php.ini. Добавьте метод __construct() и увеличьте предел памяти:

public function __construct() {

     ini_set('memory_limit', '1G'); // change as needed, as long as your system can support it

     parent::__construct(); // If added in your controller. Probably not needed if you use it in your import class

}
1 голос
/ 08 октября 2019

Расширьте свой memory_limit. Для 'localhost', в php.ini -

memory_limit=2048M

и перезагрузите сервер.

0 голосов
/ 08 октября 2019

Установите значение memory_limit неограниченным для определенного файла, чтобы переопределить php.ini memory_limit

// put it in your construct 

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