4040
4141use CodeIgniter \Database \Exceptions \DatabaseException ;
4242use CodeIgniter \Database \Exceptions \DataException ;
43+ use Closure ;
4344
4445/**
4546 * Class BaseBuilder
@@ -718,10 +719,18 @@ protected function whereHaving(string $qb_key, $key, $value = null, string $type
718719 }
719720 else
720721 {
721- $ k .= $ op ;
722+ $ k .= " $ op" ;
722723 }
723724
724- $ v = " : $ bind: " ;
725+ if ($ v instanceof Closure)
726+ {
727+ $ builder = $ this ->cleanClone ();
728+ $ v = '( ' . str_replace ("\n" , ' ' , $ v ($ builder )->getCompiledSelect ()) . ') ' ;
729+ }
730+ else
731+ {
732+ $ v = " : $ bind: " ;
733+ }
725734 }
726735 elseif (! $ this ->hasOperator ($ k ) && $ qb_key !== 'QBHaving ' )
727736 {
@@ -750,13 +759,13 @@ protected function whereHaving(string $qb_key, $key, $value = null, string $type
750759 * Generates a WHERE field IN('item', 'item') SQL query,
751760 * joined with 'AND' if appropriate.
752761 *
753- * @param string $key The field to search
754- * @param array $values The values searched on
755- * @param boolean $escape
762+ * @param string $key The field to search
763+ * @param array|Closure $values The values searched on, or anonymous function with subquery
764+ * @param boolean $escape
756765 *
757766 * @return BaseBuilder
758767 */
759- public function whereIn (string $ key = null , array $ values = null , bool $ escape = null )
768+ public function whereIn (string $ key = null , $ values = null , bool $ escape = null )
760769 {
761770 return $ this ->_whereIn ($ key , $ values , false , 'AND ' , $ escape );
762771 }
@@ -769,13 +778,13 @@ public function whereIn(string $key = null, array $values = null, bool $escape =
769778 * Generates a WHERE field IN('item', 'item') SQL query,
770779 * joined with 'OR' if appropriate.
771780 *
772- * @param string $key The field to search
773- * @param array $values The values searched on
774- * @param boolean $escape
781+ * @param string $key The field to search
782+ * @param array|Closure $values The values searched on, or anonymous function with subquery
783+ * @param boolean $escape
775784 *
776785 * @return BaseBuilder
777786 */
778- public function orWhereIn (string $ key = null , array $ values = null , bool $ escape = null )
787+ public function orWhereIn (string $ key = null , $ values = null , bool $ escape = null )
779788 {
780789 return $ this ->_whereIn ($ key , $ values , false , 'OR ' , $ escape );
781790 }
@@ -788,13 +797,13 @@ public function orWhereIn(string $key = null, array $values = null, bool $escape
788797 * Generates a WHERE field NOT IN('item', 'item') SQL query,
789798 * joined with 'AND' if appropriate.
790799 *
791- * @param string $key The field to search
792- * @param array $values The values searched on
793- * @param boolean $escape
800+ * @param string $key The field to search
801+ * @param array|Closure $values The values searched on, or anonymous function with subquery
802+ * @param boolean $escape
794803 *
795804 * @return BaseBuilder
796805 */
797- public function whereNotIn (string $ key = null , array $ values = null , bool $ escape = null )
806+ public function whereNotIn (string $ key = null , $ values = null , bool $ escape = null )
798807 {
799808 return $ this ->_whereIn ($ key , $ values , true , 'AND ' , $ escape );
800809 }
@@ -807,13 +816,13 @@ public function whereNotIn(string $key = null, array $values = null, bool $escap
807816 * Generates a WHERE field NOT IN('item', 'item') SQL query,
808817 * joined with 'OR' if appropriate.
809818 *
810- * @param string $key The field to search
811- * @param array $values The values searched on
812- * @param boolean $escape
819+ * @param string $key The field to search
820+ * @param array|Closure $values The values searched on, or anonymous function with subquery
821+ * @param boolean $escape
813822 *
814823 * @return BaseBuilder
815824 */
816- public function orWhereNotIn (string $ key = null , array $ values = null , bool $ escape = null )
825+ public function orWhereNotIn (string $ key = null , $ values = null , bool $ escape = null )
817826 {
818827 return $ this ->_whereIn ($ key , $ values , true , 'OR ' , $ escape );
819828 }
@@ -828,17 +837,17 @@ public function orWhereNotIn(string $key = null, array $values = null, bool $esc
828837 * @used-by whereNotIn()
829838 * @used-by orWhereNotIn()
830839 *
831- * @param string $key The field to search
832- * @param array $values The values searched on
833- * @param boolean $not If the statement would be IN or NOT IN
834- * @param string $type
835- * @param boolean $escape
840+ * @param string $key The field to search
841+ * @param array|Closure $values The values searched on, or anonymous function with subquery
842+ * @param boolean $not If the statement would be IN or NOT IN
843+ * @param string $type
844+ * @param boolean $escape
836845 *
837846 * @return BaseBuilder
838847 */
839- protected function _whereIn (string $ key = null , array $ values = null , bool $ not = false , string $ type = 'AND ' , bool $ escape = null )
848+ protected function _whereIn (string $ key = null , $ values = null , bool $ not = false , string $ type = 'AND ' , bool $ escape = null )
840849 {
841- if ($ key === null || $ values === null )
850+ if ($ key === null || $ values === null || (! is_array ( $ values ) && ! ( $ values instanceof Closure)) )
842851 {
843852 return $ this ;
844853 }
@@ -854,13 +863,20 @@ protected function _whereIn(string $key = null, array $values = null, bool $not
854863
855864 $ not = ($ not ) ? ' NOT ' : '' ;
856865
857- $ where_in = array_values ($ values );
858- $ ok = $ this ->setBind ($ ok , $ where_in , $ escape );
866+ if ($ values instanceof Closure)
867+ {
868+ $ builder = $ this ->cleanClone ();
869+ $ ok = str_replace ("\n" , ' ' , $ values ($ builder )->getCompiledSelect ());
870+ }
871+ else
872+ {
873+ $ ok = $ this ->setBind ($ ok , array_values ($ values ), $ escape );
874+ }
859875
860876 $ prefix = empty ($ this ->QBWhere ) ? $ this ->groupGetType ('' ) : $ this ->groupGetType ($ type );
861877
862878 $ where_in = [
863- 'condition ' => $ prefix . $ key . $ not . " IN : {$ ok }: " ,
879+ 'condition ' => $ prefix . $ key . $ not . ( $ values instanceof Closure ? " IN ( $ ok ) " : " IN : {$ ok }: ") ,
864880 'escape ' => false ,
865881 ];
866882
@@ -2676,7 +2692,6 @@ protected function compileWhereHaving(string $qb_key): string
26762692 {
26772693 continue ;
26782694 }
2679-
26802695 // $matches = array(
26812696 // 0 => '(test <= foo)', /* the whole thing */
26822697 // 1 => '(', /* optional */
@@ -3004,7 +3019,7 @@ protected function getOperator(string $str, bool $list = false)
30043019 ];
30053020 }
30063021
3007- return preg_match_all ('/ ' . implode ('| ' , $ _operators ) . '/i ' , $ str , $ match ) ? ($ list ? $ match [0 ] : $ match [0 ][count ( $ match [ 0 ]) - 1 ]) : false ;
3022+ return preg_match_all ('/ ' . implode ('| ' , $ _operators ) . '/i ' , $ str , $ match ) ? ($ list ? $ match [0 ] : $ match [0 ][0 ]) : false ;
30083023 }
30093024
30103025 // --------------------------------------------------------------------
@@ -3049,4 +3064,16 @@ protected function setBind(string $key, $value = null, bool $escape = true): str
30493064 }
30503065
30513066 //--------------------------------------------------------------------
3067+
3068+ /**
3069+ * Returns a clone of a Base Builder with reset query builder values.
3070+ *
3071+ * @return BaseBuilder
3072+ */
3073+ protected function cleanClone ()
3074+ {
3075+ return (clone $ this )->from ([], true )->resetQuery ();
3076+ }
3077+
3078+ //--------------------------------------------------------------------
30523079}
0 commit comments