본문으로 건너뛰기

모바일 상품 게시판 상세보기 조회 속도 개선

PHP 8 · GODO25
변경 파일 수: 2개
변경 파일 목록
2
변경 파일 코드
추가삭제
Component/Board/Board.php+45-5
변경 전Board.php
} else {
$extraData['arrGoodsNo'][] = $extraData['goodsNoText'];
}
foreach ($extraData['arrGoodsNo'] as $key => $goodsNo) {
$extraData['arrGoodsData'][$key] = $goods->getGoodsInfo($goodsNo);
 
 
 
 
 
 
 
 
 
}
}
if ($extraData['orderGoodsNoText']) {
$extraData['arrOrderGoodsNo'][] = $extraData['orderGoodsNoText'];
}
$order = new Order();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
foreach ($extraData['arrOrderGoodsNo'] as $key => $orderGoodsNo) {
$isAddGoods = substr($orderGoodsNo, 0, 1) == 'A' ? true : false;
if ($isAddGoods) {
$orderGoodsNo = substr($orderGoodsNo, 1);
}
$orderGoodsData = $order->getOrderGoods(null, $orderGoodsNo, null, null, null)[0];
 
 
$extraData['arrOrderGoodsData'][$key] = $orderGoodsData;
$_arrOrderGoodsData = $extraData['arrOrderGoodsData'][$key];
$goodsNo = $_arrOrderGoodsData['goodsNo'];
$extraData['arrOrderGoodsData'][$key]['goodsNo'] = $orderGoodsData['parentGoodsNo'];
$extraData['arrOrderGoodsData'][$key]['totalGoodsPrice'] = $_arrOrderGoodsData['goodsCnt'] * ($addGoodsData['goodsPrice'] + $_arrOrderGoodsData['optionPrice'] + $_arrOrderGoodsData['optionTextPrice']);
} else {
$goodsImage = $goods->getGoodsImage($goodsNo, 'main');
$goodsInfo = $goods->getGoodsInfo($goodsNo);
$goodsImageSrc = SkinUtils::imageViewStorageConfig($goodsImage[0]['goodsImageStorage'] == 'obs' ? $goodsImage[0]['imageUrl'] : $goodsImage[0]['imageName'], $goodsInfo['imagePath'], $goodsInfo['imageStorage'], 100, 'goods')[0];
$extraData['arrOrderGoodsData'][$key]['totalGoodsPrice'] = $_arrOrderGoodsData['goodsCnt'] * ($_arrOrderGoodsData['goodsPrice'] + $_arrOrderGoodsData['optionPrice'] + $_arrOrderGoodsData['optionTextPrice']);
}
 
변경 후Board.php
} else {
$extraData['arrGoodsNo'][] = $extraData['goodsNoText'];
}
// N+1 쿼리 패턴 개선: 연관상품 개별 getGoodsInfo() 반복 -> IN절 배치 1회
// (user/module Goods 오버라이드가 배치 메서드 미상속 시 fatal 방지 가드)
if (method_exists($goods, 'getGoodsInfoByNos')) {
$batchGoodsData = $goods->getGoodsInfoByNos($extraData['arrGoodsNo']);
foreach ($extraData['arrGoodsNo'] as $key => $goodsNo) {
$extraData['arrGoodsData'][$key] = $batchGoodsData[$goodsNo] ?? [];
}
} else {
foreach ($extraData['arrGoodsNo'] as $key => $goodsNo) {
$extraData['arrGoodsData'][$key] = $goods->getGoodsInfo($goodsNo);
}
}
}
if ($extraData['orderGoodsNoText']) {
$extraData['arrOrderGoodsNo'][] = $extraData['orderGoodsNoText'];
}
$order = new Order();
 
// N+1 쿼리 패턴 개선: 주문상품/이미지/정보 개별 반복 조회 -> 루프 진입 전 IN절 배치 선조회
// add-goods 의 getDataAddGoods 만 per-item 유지. user/module Goods 미상속 시 기존 per-item 폴백
$useBatch = method_exists($goods, 'getGoodsInfoByNos');
$orderGoodsMap = $goodsInfoMap = $goodsImageMap = [];
if ($useBatch) {
$snoList = [];
foreach ($extraData['arrOrderGoodsNo'] as $orderGoodsNo) {
$snoList[] = (substr($orderGoodsNo, 0, 1) == 'A') ? substr($orderGoodsNo, 1) : $orderGoodsNo;
}
$orderGoodsList = $order->getOrderGoods(null, $snoList, null, null, null);
if (is_array($orderGoodsList)) {
foreach ($orderGoodsList as $orderGoodsRow) {
$orderGoodsMap[$orderGoodsRow['sno']] = $orderGoodsRow;
}
}
$normalGoodsNos = [];
foreach ($extraData['arrOrderGoodsNo'] as $orderGoodsNo) {
if (substr($orderGoodsNo, 0, 1) == 'A') {
continue;
}
if (isset($orderGoodsMap[$orderGoodsNo]['goodsNo'])) {
$normalGoodsNos[] = $orderGoodsMap[$orderGoodsNo]['goodsNo'];
}
}
$goodsInfoMap = $goods->getGoodsInfoByNos($normalGoodsNos);
$goodsImageMap = $goods->getGoodsImageByNos($normalGoodsNos, 'main');
}
 
foreach ($extraData['arrOrderGoodsNo'] as $key => $orderGoodsNo) {
$isAddGoods = substr($orderGoodsNo, 0, 1) == 'A' ? true : false;
if ($isAddGoods) {
$orderGoodsNo = substr($orderGoodsNo, 1);
}
$orderGoodsData = $useBatch
? ($orderGoodsMap[$orderGoodsNo] ?? [])
: ($order->getOrderGoods(null, $orderGoodsNo, null, null, null)[0] ?? []);
$extraData['arrOrderGoodsData'][$key] = $orderGoodsData;
$_arrOrderGoodsData = $extraData['arrOrderGoodsData'][$key];
$goodsNo = $_arrOrderGoodsData['goodsNo'];
$extraData['arrOrderGoodsData'][$key]['goodsNo'] = $orderGoodsData['parentGoodsNo'];
$extraData['arrOrderGoodsData'][$key]['totalGoodsPrice'] = $_arrOrderGoodsData['goodsCnt'] * ($addGoodsData['goodsPrice'] + $_arrOrderGoodsData['optionPrice'] + $_arrOrderGoodsData['optionTextPrice']);
} else {
$goodsImage = $useBatch ? ($goodsImageMap[$goodsNo] ?? false) : $goods->getGoodsImage($goodsNo, 'main');
$goodsInfo = $useBatch ? ($goodsInfoMap[$goodsNo] ?? []) : $goods->getGoodsInfo($goodsNo);
$goodsImageSrc = SkinUtils::imageViewStorageConfig($goodsImage[0]['goodsImageStorage'] == 'obs' ? $goodsImage[0]['imageUrl'] : $goodsImage[0]['imageName'], $goodsInfo['imagePath'], $goodsInfo['imageStorage'], 100, 'goods')[0];
$extraData['arrOrderGoodsData'][$key]['totalGoodsPrice'] = $_arrOrderGoodsData['goodsCnt'] * ($_arrOrderGoodsData['goodsPrice'] + $_arrOrderGoodsData['optionPrice'] + $_arrOrderGoodsData['optionTextPrice']);
}
 
Component/Goods/Goods.php+86
변경 전Goods.php
 
return $infoDataContent;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후Goods.php
 
return $infoDataContent;
}
 
/**
* 상품정보 배치 조회 — getGoodsInfo() 의 IN절 배치판 (게시판 상세보기 N+1 쿼리 패턴 개선)
*
* 단건 getGoodsInfo() 를 goodsNo 건수만큼 반복 호출하던 구간을 IN 절 1회 조회로 대체한다.
*
* 게시판 상세보기 배치 전용 내부 헬퍼. 호출부(Board::getExtraData)가 method_exists 가드로 미구현(튜닝 fork) 환경을 단건 경로로 폴백하므로 외부 튜닝 확장점이 아니다. 시그니처 안정 유지를 권장한다.
*
* @param array $arrGoodsNo 상품번호 배열
* @param string|null $goodsField 출력 필드 (기본 null = 단건 getGoodsInfo 와 동일 전체 조회). goodsNo 컬럼은 결과 맵의 키이므로 부분 필드 지정 시에도 반드시 포함할 것
* @return array goodsNo 를 키로 하는 상품정보 맵 [goodsNo => row]
*/
public function getGoodsInfoByNos(array $arrGoodsNo, $goodsField = null): array
{
$arrGoodsNo = array_values(array_unique(array_filter($arrGoodsNo)));
if (empty($arrGoodsNo)) {
return [];
}
 
$arrBind = null;
$bindQuery = [];
foreach ($arrGoodsNo as $goodsNo) {
$bindQuery[] = '?';
$this->db->bind_param_push($arrBind, 'i', $goodsNo);
}
$inClause = ' g.goodsNo IN (' . implode(',', $bindQuery) . ') ';
if ($this->db->strWhere) {
$this->db->strWhere = $inClause . ' AND ' . $this->db->strWhere;
} else {
$this->db->strWhere = $inClause;
}
 
if ($goodsField) {
$this->db->strField = $this->db->strField ? $goodsField . ', ' . $this->db->strField : $goodsField;
}
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_GOODS . ' g ' . implode(' ', $query);
$getData = $this->db->secondary()->query_fetch($strSQL, $arrBind);
 
$goodsInfoMap = [];
foreach ((array) $getData as $row) {
$row = gd_htmlspecialchars_stripslashes($row);
$goodsInfoMap[$row['goodsNo']] = $row;
}
return $goodsInfoMap;
}
 
/**
* 상품이미지 배치 조회 — getGoodsImage() 의 IN절 배치판 (게시판 상세보기 N+1 쿼리 패턴 개선)
*
* 게시판 상세보기 배치 전용 내부 헬퍼. 호출부(Board::getExtraData)가 method_exists 가드로 미구현(튜닝 fork) 환경을 단건 경로로 폴백하므로 외부 튜닝 확장점이 아니다. 시그니처 안정 유지를 권장한다.
*
* @param array $arrGoodsNo 상품번호 배열
* @param string $imageKind 이미지 종류 (기본 'main')
* @return array goodsNo 를 키로 하는 이미지행 맵 [goodsNo => [row, ...]]
*/
public function getGoodsImageByNos(array $arrGoodsNo, $imageKind = 'main'): array
{
$arrGoodsNo = array_values(array_unique(array_filter($arrGoodsNo)));
if (empty($arrGoodsNo)) {
return [];
}
 
$arrBind = null;
$bindQuery = [];
foreach ($arrGoodsNo as $goodsNo) {
$bindQuery[] = '?';
$this->db->bind_param_push($arrBind, 'i', $goodsNo);
}
$this->db->bind_param_push($arrBind, 's', $imageKind);
 
// 단건 getGoodsImage 는 goodsNo 를 SELECT 에서 제외하나, 배치는 매핑 키로 필요 → goodsNo 추가
$arrField = DBTableField::setTableField('tableGoodsImage', null, 'goodsNo');
$strSQL = "SELECT goodsNo, sno, " . implode(', ', $arrField) . " FROM " . DB_GOODS_IMAGE
. " WHERE goodsNo IN (" . implode(',', $bindQuery) . ") AND imageKind = ? "
. " ORDER BY imageKind ASC, imageNo ASC";
$getData = $this->db->secondary()->query_fetch($strSQL, $arrBind);
 
$goodsImageMap = [];
foreach ((array) $getData as $row) {
$row = gd_htmlspecialchars_stripslashes($row);
$goodsImageMap[$row['goodsNo']][] = $row;
}
return $goodsImageMap;
}
}