본문 바로가기
코드이그나이터와 php7와 mysql

코드이그나이터4. 네이버검색 따라잡기-4. 한글풀어쓰기 검색

1. 오토셋 APM 인스톨러 ( apache + php7.2 + mariadb ) 설치 | https://itadventure.tistory.com/93

2. 코드이그나이터 4 ( codeigniter 4 ) 설치 | https://itadventure.tistory.com/95

3. 비주얼 스튜디오 코드 에디터 설치 & 한글 설정 | https://itadventure.tistory.com/96

4. 폴더열기 / 웹페이지 편집(1) | https://itadventure.tistory.com/97

5. 웹페이지 편집(2) | https://itadventure.tistory.com/101

6. 코드이그나이터4의 URL 규칙 | https://itadventure.tistory.com/105

7. php, 네임스페이스 [ namespace ] ?! | https://itadventure.tistory.com/118

8. 코드이그나이터의 네임스페이스, 그리고 모델 | https://itadventure.tistory.com/122

9. 코드이그나이터 뷰의 파라미터 전달 | https://itadventure.tistory.com/147

10. 코드이그나이터 뷰를 나눠 볼까요? | https://itadventure.tistory.com/174

11. MYSQL 이 뭐여? [ 마이에스큐엘은 서류철이다! ] | https://itadventure.tistory.com/175

12. MYSQL 콘솔에 접속해보자! | https://itadventure.tistory.com/178

13. MySql에 넣었다가 꺼냈다가, 뭘? | https://itadventure.tistory.com/265

14. 검색진열대 MYSQL | https://itadventure.tistory.com/267

15. 편집의 왕자 MySQL | https://itadventure.tistory.com/269

16. 코드이그나이터4, MYSQL과 손잡다. | https://itadventure.tistory.com/271

17. MySQL -> 컨트롤러 -> 뷰 트리플 패스! | https://itadventure.tistory.com/272

18. 코드이그나이터! MySQL 에 입력하다! ( insert ) | https://itadventure.tistory.com/273

19. 해킹을 막아라! MySQL인젝션 보안 | https://itadventure.tistory.com/274

20. MySQL과 친해지는 phpmyadmin | https://itadventure.tistory.com/277

21. 코드이그나이터4에서 책 정보를 편집해볼까요? | https://itadventure.tistory.com/280

22. 코드이그나이터4에서 책을 지워봅시다. DELETE! | https://itadventure.tistory.com/282

23. 코드이그나이터4, 페이징 기술 | https://itadventure.tistory.com/285

24. 코드이그나이터4. 검색! | https://itadventure.tistory.com/295

25. 코드이그나이터4. 검색어 자동 추천! | https://itadventure.tistory.com/303

26. 코드이그나이터4. 네이버 검색 따라잡기-1. 대용량 자료 쾌속 검색!(1) | https://itadventure.tistory.com/304

27. 코드이그나이터4. 네이버 검색 따라잡기-2. 대용량 자료 쾌속 검색!(2) | https://itadventure.tistory.com/306

28. 코드이그나이터4. 네이버 검색 따라잡기-3. 대용량 자료 쾌속 검색!(3) | itadventure.tistory.com/310

29. 코드이그나이터4. 코드이그나이터답게 모델화! M!(Model) | itadventure.tistory.com/363

30. 코드이그나이터4. 네이버검색 따라잡기-4. 한글풀어쓰기 검색


지난번에 만든 크레이한글 클래스는 사실 이번 강좌를 위한 것입니다.

itadventure.tistory.com/364

 

PHP '크레이한글' 클래스 - 한글 조합(오토마타) / 자소 분리

한글 오토마타라고 들어보셨나요? 우리가 키보드를 통해 한글을 타이핑할 때 내부에서는 일련의 복잡한 조합과정을 거칩니다. 이를 테면 'ㄱ'자를 타이핑하고 'ㅏ'를 '가' 라는 글자가 완성되고,

itadventure.tistory.com

네이버 검색을 유심히 살펴보다 보면 검색하는 부분에 아주 특화된 기능이 숨어 있는 것을 보실 수 있습니다.
이른바 한글 자소 검색인데요.
만약 스니커를 검색하려고 '스니ㅋ' 까지만 타이핑하면 스니 + ㅋ 으로 시작되는 단어들이 주루륵 나옵니다.

그래서 한글을 모두 타이핑하지 않고 단어를 선택해서 검색할 수가 있는데요.
어떤 원리일까요?

크레이는 네이버 개발자가 아니라서 자세한 원리는 알 수는 없지만,
아마도 각 내용도 한글 풀어쓰기로 저장, 검색 기능도 한글 풀어쓰기로 검색하기 때문일 것으로 추정됩니다.

이를테면 '스니커즈' 를 'ㅅㅡㄴㅣㅋㅓㅈㅡ'로 보관한다면
검색할 때 'ㅅㅡㄴㅣㅋ'까지만 타이핑해도 앞의 문장이 같으니까 검색이 되는 원리이지요.

이번 시간에는 이걸 만들어 볼겁니다.
역시 주저리 주저리 설명드리면 본문이 너무 길어지니 변경된 부분과 간략한 설명으로 진행하겠습니다.

우선 지난번 공개해드린 크레이한글 클래스를 코드이그나이터용으로 수정하였습니다.

app\Models\CrayHangulModel.php 파일로 저장해주세요.
그냥 라이브러리라고 생각하고 사용해주시면 됩니다. 설명드리기에는 꽤 내용이 깁니다.

노파심에 말씀드리지만 한글이 들어 있어 utf-8 형식으로 저장해 주셔야 합니다.
비주얼 스튜디오 코드 에디터를 사용하면 자동으로 utf-8이니 추가로 하실 일은 없습니다.

<?php namespace App\Models;

// 제목 : 크레이한글 클래스 ( UTF-8 )
// 설명 : 한글 자소 분리, 한글 조합을 자유롭게 수행합니다.
// 제작자 : 크레이 ( 이용운 )
// 첫오픈 : 2020. 10. 10
// 블로그 : 크레이의 IT 탐구 / https://itadventure.tistory.com
// 주석만 삭제하지 않으면 자유롭게 사용하셔도 좋습니다.
class CrayHangulModel {
	private $cho_start=0xac00; // 초성이 시작되는 코드
	private $cho_length=588; //  초성간 간격
	private $jung_length=28; //  중성간 간격

	// 초성 19글자
	private $cho_char=array(
		"ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ",
		"ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ",
		"ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ",
		"ㅋ", "ㅌ", "ㅍ", "ㅎ");

	// 중성 21글자
	private $jung_char=array(
		"ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ",
		"ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ",
		"ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ",
		"ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ",
		"ㅣ"
	);

	// 중성 27글자 + 공백1개 ( 받침이 없는 경우 )
	private $jong_char=array(
		"", "ㄱ", "ㄲ", "ㄳ", "ㄴ",
		"ㄵ", "ㄶ", "ㄷ", "ㄹ", "ㄺ",
		"ㄻ", "ㄼ", "ㄽ", "ㄾ", "ㄿ",
		"ㅀ", "ㅁ", "ㅂ", "ㅄ", "ㅅ",
		"ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ",
		"ㅌ", "ㅍ", "ㅎ"
	);

	// 중성 - 겹자음 자소글자를 두 글자로 나눈 것
	private $jong_char2=array(
		"", "ㄱ", "ㄲ", "ㄱㅅ", "ㄴ",
		"ㄴㅈ", "ㄴㅎ", "ㄷ", "ㄹ", "ㄹㄱ",
		"ㄹㅁ", "ㄹㅂ", "ㄹㅅ", "ㄹㅌ", "ㄹㅍ",
		"ㄹㅎ", "ㅁ", "ㅂ", "ㅂㅅ", "ㅅ",
		"ㅆ", "ㅇ", "ㅈ", "ㅊ", "ㅋ",
		"ㅌ", "ㅍ", "ㅎ"
	);

	// 한글 한글자를 3개의 초중종성 글자 배열로 분할해줍니다. ( 3바이트 UTF8 기준 )
	// 입력)
	//   $char : UTF8 한글 한글자
	//   $jong_split : 종성이 2개의 자소인 경우 분리할지 여부, ㄳ => ㄱ, ㅅ ( 기본 = false )
	// 리턴)
	//   한글자소를 배열로 리턴합니다.
	public function split_han($char, $jong_split=false)
	{
		// 1바이트 코드인 경우
		if(strlen($char)<=1)return array($char);

		// UTF8 코드표 주소 추출
		$c1=substr($char, 0, 1);
		$c2=substr($char, 1, 1);
		$c3=substr($char, 2, 1);
		$p=ord($c1) * 65536 + ord($c2) * 256 + ord($c3); 

		// 한글이 아닌 경우
		if($p<0xeab080 || $p>0xed9ea3 )return array($char);
		
		// 1110XXXX 10XXXXXX 10XXXXXX
		// UNICODE 코드 추출
		$unicode = 
			(( ord($c1) & 15 ) << 12) +
			(( ord($c2) & 0x3f ) << 6) +
			(( ord($c3) & 0x3f ));

		// 한글 인덱스
		$hanindex = $unicode - $this->cho_start;

		// 초성 추출
		$cho=floor($hanindex / $this->cho_length);
		$hanindex-=$cho * $this->cho_length;
		$jung=floor($hanindex / $this->jung_length);
		$hanindex-=$jung * $this->jung_length;
		$jong=$hanindex;

		if($jong_split==false)
			$jongarr = $this->jong_char[$jong];
		else 
			$jongarr = $this->jong_char2[$jong];

		// echo $unicode." ( $cho / $jung / $jong ) <br/>";

		if(strlen($jongarr)==6)
			$jong_array=array(
				substr($jongarr,0,3),
				substr($jongarr,3,3)
			);
		else if(strlen($jongarr)==3)
			$jong_array=array($jongarr);
		else
			$jong_array=array();

		return array_merge(
			array(
				$this->cho_char[$cho],
				$this->jung_char[$jung]
			),
			$jong_array
		);
	}

	// 한글 조합, 한글 자소 배열을 입력, 합체된 한글 한글자를 얻습니다.
	// 입력)
	//   $chars : 한글 자소 배열. 예) array("ㄱ", "ㅏ", "ㄹ");
	// 리턴)
	//   합쳐진 한글 한글자를 리턴합니다.
	public function join_han($chars)
	{
		if(count($chars)<=1)return implode("", $chars);
		// 초성이 없으면 그냥 원본 리턴
		$cho=array_search($chars[0], $this->cho_char);
		if($cho===false)return implode("", $chars);

		// 중성이 없으면 그냥 원본 리턴
		$jung=array_search($chars[1], $this->jung_char);
		$jung2=array_search($chars[2], $this->jung_char);

		$jong_start=2;

		// 겹모음 예외 처리
		if($chars[1]=="ㅗ" && $chars[2]=="ㅏ"){ 
			$jung=array_search("ㅘ", $this->jung_char); 
			$jong_start++; 
		}
		else if($chars[1]=="ㅗ" && $chars[2]=="ㅐ"){ 
			$jung=array_search("ㅙ", $this->jung_char); 
			$jong_start++; 
		}
		else if($chars[1]=="ㅗ" && $chars[2]=="ㅣ"){ 
			$jung=array_search("ㅚ", $this->jung_char); 
			$jong_start++; 
		}
		else if($chars[1]=="ㅜ" && $chars[2]=="ㅓ"){ 
			$jung=array_search("ㅝ", $this->jung_char); 
			$jong_start++; 
		}
		else if($chars[1]=="ㅜ" && $chars[2]=="ㅔ"){ 
			$jung=array_search("ㅞ", $this->jung_char); 
			$jong_start++; 
		}
		else if($chars[1]=="ㅡ" && $chars[2]=="ㅣ"){ 
			$jung=array_search("ㅢ", $this->jung_char); 
			$jong_start++; 
		}


		if($jung===false)return implode("", $chars);
		// 종성은 합쳐서 조사
		$jongstr="";		
		for($i=$jong_start;$i<count($chars);++$i)
			$jongstr.=$chars[$i];
		$jong=array_search($jongstr, $this->jong_char);
		// 종성글자가 나눠졌을 수 있으니 한번 더 조사
		if($jong===false)$jong=array_search($jongstr, $this->jong_char2);
		// 종성이 있는데도 못 찾은 경우
		if(strlen($jongstr)>0 && $jong===false)
		{
			// 종성 한글자만 찾아서 넣는다.
			$jong=array_search($chars[$jong_start], $this->jong_char);
			$addret="";
			for($i=$jong_start+1;$i<count($chars);++$i)
				$addret.=$chars[$i];
		}

		$unicode=$this->cho_start + $cho*$this->cho_length + $jung*$this->jung_length + $jong;
		// $unicode=chr($unicode >> 8).chr($unicode & 0xff);
		// XXXX XXXX XX XXXXXX
		// 1110XXXX 10XXXX XX 10XXXXXX
		$utf8code= 
			( ( ($unicode & 0xf000) << 4 ) | 0xe00000 ) +
			( ( ($unicode & 0x0fc0) << 2 ) | 0x008000 ) + 
			( ( ($unicode & 0x003f) ) | 0x00080 );
		$utf8=chr($utf8code>>16).chr(($utf8code>>8)&0xff).chr($utf8code&0xff);
		return $utf8.$addret;
	}

	// 한글 풀어쓰기 - UTF8 전용
	// 입력)
	//   $str : 문장 - 한영 혼용 가능
	//   $jong_split : 종성이 2개의 자소인 경우 분리할지 여부, ㄳ => ㄱ, ㅅ ( 기본 = false )
	// 리턴)
	//   풀어쓰기한 자소한글 문장을 리턴합니다.
	public function hangulPuli($str, $jong_split=false)
	{
		$result="";
		for($col=0;$col<strlen($str);++$col)
		{
			$c=substr($str,$col,1);
			if((ord($c)&0x80)==0) // 일반 글자
				$result.=$c;
			else {
				$c.=substr($str, $col+1, 2);
				$col+=2;
				$result.=implode("", $this->split_han($c, $jong_split));
			}
		}
		return $result;
	}

	// 한글 조합 - UTF8 전용
	// 입력)
	//   $str : 문장 - 한글 자소로 구성된 문장
	// 리턴)
	//   모아쓰기한 자소한글 문장을 리턴합니다.
	public function hangulJohap($str)
	{
		$str=$this->hangulPuli($str, true);

		// 자소 단위로 배열에 넣는다
		$chars=array();
		$cut=array();
		for($col=0,$cnt=0;$col<strlen($str);++$col)
		{
			$c=substr($str,$col,1);
			if((ord($c)&0x80)==0){ // 일반 글자
				$chars[$cnt]=$c;
				$cut[$cnt]=true;
				$cnt++;
			}
			else {
				$c.=substr($str, $col+1, 2);
				$col+=2;
				$chars[$cnt]=$c;
				if(in_array($c, $this->jung_char)) { // 모음인 경우
					// 바로 앞글자가 자음인 경우만
					if(in_array($chars[$cnt-1], $this->cho_char)) { 
						$cut[$cnt-1]=true; // 해당 부분 커트 정의
					}
				}
				$cnt++;
			}
		}

		// cut 단위로 문장 재구성
		$result="";
		$cc=array();
		for($i=0;$i<count($chars);++$i)
		{
			if($cut[$i]==true){
				$result.=$this->join_han($cc);
				$cc=array($chars[$i]);
			}
			else {
				$cc[]=$chars[$i];
			}
		}
		$result.=$this->join_han($cc);
		return $result;
	}

	// 뒤에서 자소 1글자를 삭제
	// 입력)
	//   $str : 문장
	// 리턴)
	//   자소 1개를 삭제한 문장을 리턴
	public function str_backspace($str){
		$str=$this->hangulPuli($str, true);

		// 자소 단위로 배열에 넣는다
		$chars=array();
		for($col=0,$cnt=0;$col<strlen($str);++$col)
		{
			$c=substr($str,$col,1);
			if((ord($c)&0x80)==0){ // 일반 글자
				$chars[$cnt]=$c;
				$cnt++;
			}
			else {
				$c.=substr($str, $col+1, 2);
				$col+=2;
				$chars[$cnt]=$c;
				$cnt++;
			}
		}

		// 마지막 글자를 제외하고 모두 합침
		$result="";
		for($i=0;$i<count($chars)-1;++$i)
		{
			$result.=$chars[$i];
		}
		return $this->hangulJohap($result);
	}
};

?>

그 외 수정된 소스들입니다.
이 파일은 app\Models\BookModel.php 이고

<?php namespace App\Models;

use CodeIgniter\Model;
use App\Models\CrayHangulModel;

class BookModel extends Model
{
    public $db;

    // 클래스 생성자
    public function __construct()
    {
        $this->db = \Config\Database::connect("default", false);
    }

    public function get_query()    
    {        
        $sql = "select * FROM book";
        return $this->db->query($sql);
    }

    // 단어 사전 만들기
    public function MakeBookWord()
    {
        // 크레이한글 모델 클래스
        $CrayHangul = new CrayHangulModel();
        ini_set('memory_limit', -1);
        $query_cnt = $this->db->query("SELECT count(*) as cnt FROM book");
        $total = $query_cnt->getResultArray()[0]['cnt'];        
        $limit=1000; // 1000 건씩 끊어서 처리
        $word_arr=array();

        $special_chars=array(
            "[", "]", "(", ")", ",", ":", ";", "<", ">",
            "&", "?"
        );
        // 유지할 단어 + - * / . %
        for($step=0;$step<$total;$step+=$limit)
        {
            $results = $this->db->query(
                "SELECT number, title FROM book 
                order by number 
                limit $step, $limit"
            )->getResultArray();
            foreach($results as $result)
            {
                foreach($special_chars as $char){
                    $result['title']=str_replace($char, " ", $result['title']);
                }
                $explode = array_unique(explode(" ", trim($result['title'])));
                foreach($explode as $word)
                {
                    if(strlen($word)<=1)continue;                    
                    if(!empty($word_arr[$word]))$word_arr[$word].=",";
                    $word_arr[$word].=$result['number'];
                }                    
            }
        }

        echo count($word_arr)."개<br/>";
        ob_flush(); flush();

        $cnt=0;
        $this->db->query("drop table book_search");
        $this->db->query("create table book_search (
            word varchar(120),
            numbers text
        )");
        $i=0;
        $this->db->transStart();
        foreach($word_arr as $word=>$numbers){
            $i++;
            $word_puli = $CrayHangul->hangulPuli($word);
            echo "$i: $word => $word_puli / ".$numbers."<br/>";
            $sql="insert into book_search (word, numbers)
            values ('".addslashes($word_puli)."', '".addslashes($numbers)."')";
            $this->db->query($sql);
            $cnt++;
            if($cnt>=100)
            {
                $this->db->transComplete();
                $this->db->transStart();
                $cnt=0;
            }
        }
        $this->db->transComplete();
        $this->db->query("ALTER TABLE book_search ADD INDEX(word);");
        
        echo "단어사전 입력이 완료되었습니다.";
    }

    // 책 삽입    
    public function BookInsert($data)
    {
        $set=array();
        foreach($data as $fld=>$value)
            $set[]="$fld='".
            $this->db->escapeString($value).
            "'";
        $this->db->query(
            "insert into book set ".
            implode(", ", $set)
        );
    }

    // 책 수정
    public function BookEdit($number, $data)
    {
        $set=array();
        foreach($data as $fld=>$value)
            $set[]="$fld='".
            $this->db->escapeString($value).
            "'";
        $this->db->query("update book set ".
            implode(", ", $set).
            "where number=".$number." limit 1"
        );
    }

    // 책 삭제
    public function BookDelete($number)
    {
        $this->db->query("delete from book
            where number=".$number." limit 1"
        );
    }

    // 검색조건에 따른 갯수, where 쿼리문 추출
    public function BookSearchCount($searchword)
    {
        // 크레이한글 모델 클래스
        $CrayHangul = new CrayHangulModel();
        $searchword=$CrayHangul->hangulPuli($searchword);
        $where_sql="";
        if($searchword!=""){
            $query_search=$this->db->query(
                $sql="SELECT numbers FROM book_search 
                where word like '".addslashes($searchword)."%'");
            $results_search = $query_search->getResultArray();
            $numbers = array();
            foreach($results_search as $one)
            {
                $tmp=explode(",", $one['numbers']);
                $numbers = array_merge($numbers, $tmp);
            }
            $total=count($numbers);
            sort($numbers);
            $numbers=implode(",", $numbers);
            if($numbers!="")
                $where_sql="where number in ($numbers)";
            else $where_sql="where 0";
        }
        else 
        {
            $query_count = $this->db->query(
                "SELECT count(*) FROM book $where_sql");
            $results_count = $query_count->getResultArray();
            $total = $results_count[0]['count(*)'];    
        }
        return array($where_sql, $total);
    }

    // ajax 쿼리
    function BookSearchAjax($searchword)
    {
        // 크레이한글 모델 클래스
        $CrayHangul = new CrayHangulModel();
        $searchword=$CrayHangul->hangulPuli($searchword);
        $query = $this->db->query(
            "SELECT numbers FROM book_search 
            where word like '".addslashes($searchword)."%'");
        $results = $query->getResultArray();
        $numbers=array();
        foreach($results as $result)
            $numbers=array_merge(
                $numbers, 
                explode(",", $result['numbers']));
        sort($numbers);
        $numbers=implode(",", $numbers);
        if($numbers=="")die();
        
        $query = $this->db->query(
            "SELECT distinct title FROM book where 
            number in ($numbers) limit 10");
        $results = $query->getResultArray();                
        return $results;
    }

}

단어사전을 만드는 app\Controllers\MakeBookWord2.php 파일입니다.
BookModel 클래스에 기능을 넣어버려서 아주 간단해졌습니다.

<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use App\Models\BookModel;

class MakeBookWord2 extends Controller
{
    public function index()
    {        
        // 도서에 관련된 기능이 내장된 클래스를 오브젝트 생성
        $BookModel = new BookModel();
        // 사전을 만드는 기능 실행
        $BookModel->MakeBookWord();
    }
}

책을 검색하는 목록 app\Controllers\BookList7.php  파일입니다.
검색 쿼리 부분이 BookModel 로 빠져서 좀 더 심플해졌습니다.

<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use App\Models\BookModel;

class BookList7 extends Controller
{
    function get_time() {
        $t=explode(" ",microtime());
        return (float)$t[0]+(int)$t[1]; 
    }
    public function index()
    {       
        $start = $this->get_time();
        $db = \Config\Database::connect("default", false);
        $page=$this->request->getVar("page");
        $searchword=trim($this->request->getVar("searchword"));

        $BookModel = new BookModel();
        list($where_sql, $total)=$BookModel->BookSearchCount($searchword);

        $pageview=20;
        if($page=="")$page=1;
        $startlimit=($page-1)*$pageview;
        $totalpage = ceil($total / $pageview);
        
        // 페이지 목록
        $page_begin=$page;
        if($page_begin>$totalpage-10)$page_begin=$totalpage-10;
        if($page_begin<1)$page_begin=1;
        $page_end=$page+9;
        if($page_end>$totalpage)$page_end=$totalpage;
        
        $pagestr="{$total}건, {$totalpage}페이지 / ";
        if($page>1)
            $pagestr .= 
                "<a href='?page=1&searchword=$searchword' 
                style='text-decoration:none'>처음</a> ... ";
        for($i=$page_begin;$i<=$page_end;++$i){
            $pagestr .= 
                "<a href='?page=$i&searchword=$searchword' 
                style='text-decoration:none'>";
            if($i==$page)
                $pagestr .= 
                    "<span style='color:red;font-weight:bold'>[";
            $pagestr .= "$i";
            if($i==$page)$pagestr .= "</span>]";
            $pagestr .= "</a> ";
        }

        if($i-1<$totalpage)
            $pagestr .= 
                " ... <a href='?page=$totalpage&searchword=$searchword' 
                style='text-decoration:none'>끝</a>";

        $query = $db->query(
            "SELECT * FROM book $where_sql 
            order by number desc 
            limit $startlimit, $pageview");    
        $results = $query->getResultArray();
        $data = [
            'title'=>"도서 목록",
            'booklist'=>$results,
            'pagestr'=>$pagestr,
            'searchword'=>$searchword
        ];
        $end = $this->get_time();
        echo "응답속도:".($end - $start)."<br/>";
        return view('booklist7', $data);
    }
}

타이핑할 때 실행되는 ajax 페이지 app\Conrollers\BookAjax7.php 파일도
BookModel 로 기능을 넣어버려 간단해졌습니다.

<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use App\Models\BookModel;

class BookAjax7 extends Controller
{
    public function index()
    {
        $db = \Config\Database::connect("default", false);
        $BookModel = new BookModel();
        $searchword=trim($this->request->getVar("searchword"));
        $results = $BookModel->BookSearchAjax($searchword);
        $data = [
            'booklist'=>$results
        ];        
        return view('bookajax', $data);
    }
}

책목록 스킨 파일이 변경된 페이지들을 호출하기 위해 링크들이 변경되었습니다.
app\Views\booklist7.php

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
    .title_style { font-size:12pt; color:blue}
    .author_style { font-size:9pt; color:gray}
    #preview {
        top:0px;left:0px;
        width:500px;height:200px;
        border:1px solid black;`
        background-color:white;
        overflow:auto;
    }
</style>
<script>
function findbook()
{
  if($('#searchword').val()=='')
  {
      $("#preview").hide();
      return;
  }
  $.ajax({
    url: "./bookAjax7",
    type: "POST",
    data: {searchword : $('#searchword').val() },
    dataType: "html",
    success: function(data) {
      $("#preview").html(data);
      $("#preview").show();
    }
  });  
}

function search_input(obj)
{
    $("#searchword").val($(obj).text());
    document.frm.submit();
}
</script>
<u><b><?=$title?></b></u><br/>
<?=$pagestr?>
<form name="frm" method=post action="bookList7">
제목 검색 : <input type=text name=searchword id=searchword value="<?=$searchword?>" onkeyup="findbook()">
<input type=submit value="검색">
<input type=button value="단어사전만들기" onclick="location.href='MakeBookWord2'">
<input type=button value="책 입력" onclick="location.href='bookInput'">
</form>
<div style="position:relative">
    <div id="preview" style="position:absolute;display:none;background-color:white">

    </div>
</div>
<hr/>
<? foreach ($booklist as $book){?>
<span class=title_style><?= $book['title']?></span>
<span class=author_style>/ <?= $book['author']?></span>
<span class=author_style>/ <a href="bookEdit?number=<?= $book['number']?>">[편집]</a></span>
<span class=author_style>/ <a href="bookDelete?number=<?= $book['number']?>">[삭제]</a></span>
<br/>
<? } ?>
<hr/>
<?=$pagestr?><br/>

여기까지가 수정된 파일의 전부인데요.
변경된 부분만 살펴보겠습니다.

제일 먼저 단어 사전을 생성하는 부분이 변경되었는데요.
단어사전을 생성하는 MakeBookWord2.php 파일의 소스에서 use 문을 제외하면 단 2줄로 단어 사전을 생성하도록 되어 있습니다. BookModel 의 MakeBookWord 메소드를 호출하는 것이지요.

use App\Models\BookModel;

      :
      
    // 도서에 관련된 기능이 내장된 클래스를 오브젝트 생성
    $BookModel = new BookModel();
    // 사전을 만드는 기능 실행
    $BookModel->MakeBookWord();

원래 여기 있던 소스가 BookModel 에 하나의 메소드로 들어갔다고 보시면 됩니다.

// 단어 사전 만들기
public function MakeBookWord()
{
    :
}    

그런데 한가지 변경된 부분이 있습니다.
그건 바로 한글 풀어쓰기 인데요.
단어 사전을 만들때 한글을 풀어쓰기로 보관합니다.
아래와 같이 말이지요.

이 부분의 소스는 아래와 같습니다.
크레이 한글 클래스를 이용해서 한글풀어쓰기 기능을 적용한 다음 테이블에 저장할 때 사용하는 것이지요.

// 크레이한글 모델 클래스
$CrayHangul = new CrayHangulModel();
        
        :
        
    $word_puli = $CrayHangul->hangulPuli($word);
    echo "$i: $word => $word_puli / ".$numbers."<br/>";
    $sql="insert into book_search (word, numbers)
    values ('".addslashes($word_puli)."', '".addslashes($numbers)."')";

검색하는 부분도 마찬가지입니다.
역시 BookModel 클래스의 BookSearchCount() 메소트로 관련 내용이 편입되었는데요.
검색을 시작하기 전에 먼저 풀어쓰기로 바꿔 놓고 시작합니다. 타이핑할 때 보이는 Ajax 검색도 마찬가지이지요.

public function BookSearchCount($searchword)
{
    // 크레이한글 모델 클래스
    $CrayHangul = new CrayHangulModel();
    $searchword=$CrayHangul->hangulPuli($searchword);

변경 내용을 http://localhost/bookList7 페이지에서 작동시키면 그 결과는 아래와 같습니다.
'단ㅍ' 까지만 입력하면 '단편'으로 시작하는 도서들이 검색되고,
'블ㄹ' 까지만 입력하면 '블로그', '블록체인', '블랙', '블라드' 등의 책들이 함께 검색되는 것이지요,.

오늘도 필요하신 분에게 도움이 되셨는지 모르겠습니다 :)

즐거운 주말들 되시길 바랍니다.
여기까지 읽어주셔서 감사합니다.