glmdev
7/6/2018 - 3:17 PM

search.php

<?php

/**
 * Searches the collection of given $models for
 * $string and returns the results.
 *
 * @param \Illuminate\Support\Collection $models
 * @param $string
 * @param $fields
 * @return \Illuminate\Support\Collection
 */
public static function search( $models, $string, $fields ){
	// define the model container in the correct scope
	$model = null;
	
	// sanitize and format the query
	$query = formatQuery( $string );
	
	// initialize working arrays
	$returns = [];
	$toOpt = [];
	$optHits = [];
	$orgHits = [];

	/**
	 * Count the number of times each word in the search
	 * string is found in the $item's searchable fields
	 * and push those to an array.
	 */
	/* @var $item \Illuminate\Database\Eloquent\Model */
	foreach ( $models as $item ){
		$counts = [];
		foreach ( $query as $word ){
			$wordcount = 0;
			foreach ( $fields as $field ){
				$wordcount = $wordcount + substr_count( strtolower( $item->$field ), $word );
			}
			$counts[ $word ] = $wordcount;
		}
		array_push( $toOpt, [
			'counts' => $counts,
			'item'   => $item
		]);
	};

	/**
	 * Convert the word found count for each individual
	 * word to a single unified hit count, adjusting
	 * for common words and letters and push to an
	 * array.
	 */
	foreach ( $toOpt as $formattedItem ){
		$hits = 0;
		foreach ( $formattedItem['counts'] as $word => $count ){
			if ( strlen( $word ) == 1 ){
				$hits += ($count*0.1);
			}
			elseif ( strlen( $word ) == 2 ){
				$hits += ($count*0.3);
			}
			else {
				$hits += $count;
			}
		}
		array_push( $optHits, [
			'hits' => $hits,
			'item' => $formattedItem['item']
		]);
	}

	/**
	 * Sort the items in an array based on hit count.
	 * Where the key is the number of hits, push all
	 * items with that specific number of hits to
	 * the sub array.
	 */
	foreach ( $optHits as $hitCountItem ){
		if ( isset( $orgHits[ (string) $hitCountItem['hits'] ] ) ){
			array_push( $orgHits[ (string) $hitCountItem['hits'] ], $hitCountItem['item'] );
		}
		else {
			$orgHits[ (string) $hitCountItem['hits'] ] = [ $hitCountItem['item'] ];
		}
	}

	/**
	 * Sort the array by the number of hits
	 * from low to high. So, the items with
	 * least hits to highest hits, still
	 * grouped.
	 */
	ksort( $orgHits );

	/**
	 * Push each item to a single-dimensional array
	 * in order of least number of hits to highest
	 * number of hits.
	 */
	foreach ( $orgHits as $hitCountGroup ){
		foreach ( $hitCountGroup as $item ){
			array_push( $returns, $item );
		}
	}

	/**
	 * Reverse the order of the array
	 * so that the first item has the
	 * greatest number of hits, and
	 * the last one has the least.
	 */
	$returns = array_reverse( $returns );

	/**
	 * return the array in the form of a
	 * Laravel model collection
	 */
	return collect( $returns );
}

/**
 * Formats the string into an array of non-punctuated,
 * non-duplicated, lowercase words.
 *
 * @param $string
 * @return array
 */
public static function formatQuery( $string ){
	$words = explode(' ', $string);
	$return = [];
	foreach ( $words as $word ){
		$word = preg_replace('/[^[:alpha:]]/', '', $word); // removes all punctuation
		$word = strtolower( $word );
		if ( ! in_array( $word, $return ) ) {
			array_push($return, $word);
		}
	}

	return $return;
}