Page Menu
Home
WMGMC Issues
搜索
Configure Global Search
登录
Files
F16345
Builder.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
订阅
标记用于日后
授予令牌
Size
49 KB
Referenced Files
None
订阅者
None
Builder.php
View Options
<?php
namespace
LdapRecord\Query
;
use
BadMethodCallException
;
use
Closure
;
use
DateTimeInterface
;
use
InvalidArgumentException
;
use
LDAP\Result
;
use
LdapRecord\Connection
;
use
LdapRecord\Container
;
use
LdapRecord\EscapesValues
;
use
LdapRecord\LdapInterface
;
use
LdapRecord\LdapRecordException
;
use
LdapRecord\Models\Model
;
use
LdapRecord\Query\Events\QueryExecuted
;
use
LdapRecord\Query\Model\Builder
as
ModelBuilder
;
use
LdapRecord\Query\Pagination\LazyPaginator
;
use
LdapRecord\Query\Pagination\Paginator
;
use
LdapRecord\Support\Arr
;
use
LdapRecord\Utilities
;
class
Builder
{
use
EscapesValues
;
/**
* The selected columns to retrieve on the query.
*
* @var array
*/
public
$columns
;
/**
* The query filters.
*
* @var array
*/
public
$filters
=
[
'and'
=>
[],
'or'
=>
[],
'raw'
=>
[],
];
/**
* The LDAP server controls to be sent.
*
* @var array
*/
public
$controls
=
[];
/**
* The LDAP server controls that were processed.
*
* @var array
*/
public
$controlsResponse
=
[];
/**
* The size limit of the query.
*
* @var int
*/
public
$limit
=
0
;
/**
* Determines whether the current query is paginated.
*
* @var bool
*/
public
$paginated
=
false
;
/**
* The distinguished name to perform searches upon.
*
* @var string|null
*/
protected
$dn
;
/**
* The base distinguished name to perform searches inside.
*
* @var string|null
*/
protected
$baseDn
;
/**
* The default query type.
*
* @var string
*/
protected
$type
=
'search'
;
/**
* Determines whether the query is nested.
*
* @var bool
*/
protected
$nested
=
false
;
/**
* Determines whether the query should be cached.
*
* @var bool
*/
protected
$caching
=
false
;
/**
* How long the query should be cached until.
*
* @var DateTimeInterface|null
*/
protected
$cacheUntil
=
null
;
/**
* Determines whether the query cache must be flushed.
*
* @var bool
*/
protected
$flushCache
=
false
;
/**
* The current connection instance.
*
* @var Connection
*/
protected
$connection
;
/**
* The current grammar instance.
*
* @var Grammar
*/
protected
$grammar
;
/**
* The current cache instance.
*
* @var Cache|null
*/
protected
$cache
;
/**
* Constructor.
*
* @param Connection $connection
*/
public
function
__construct
(
Connection
$connection
)
{
$this
->
connection
=
$connection
;
$this
->
grammar
=
new
Grammar
();
}
/**
* Set the current connection.
*
* @param Connection $connection
* @return $this
*/
public
function
setConnection
(
Connection
$connection
)
{
$this
->
connection
=
$connection
;
return
$this
;
}
/**
* Set the current filter grammar.
*
* @param Grammar $grammar
* @return $this
*/
public
function
setGrammar
(
Grammar
$grammar
)
{
$this
->
grammar
=
$grammar
;
return
$this
;
}
/**
* Set the cache to store query results.
*
* @param Cache|null $cache
* @return $this
*/
public
function
setCache
(
Cache
$cache
=
null
)
{
$this
->
cache
=
$cache
;
return
$this
;
}
/**
* Returns a new Query Builder instance.
*
* @param string $baseDn
* @return $this
*/
public
function
newInstance
(
$baseDn
=
null
)
{
// We'll set the base DN of the new Builder so
// developers don't need to do this manually.
$dn
=
is_null
(
$baseDn
)
?
$this
->
getDn
()
:
$baseDn
;
return
(
new
static
(
$this
->
connection
))->
setDn
(
$dn
);
}
/**
* Returns a new nested Query Builder instance.
*
* @param Closure|null $closure
* @return $this
*/
public
function
newNestedInstance
(
Closure
$closure
=
null
)
{
$query
=
$this
->
newInstance
()->
nested
();
if
(
$closure
)
{
$closure
(
$query
);
}
return
$query
;
}
/**
* Executes the LDAP query.
*
* @param string|array $columns
* @return Collection|array
*/
public
function
get
(
$columns
=
[
'*'
])
{
return
$this
->
onceWithColumns
(
Arr
::
wrap
(
$columns
),
function
()
{
return
$this
->
query
(
$this
->
getQuery
());
});
}
/**
* Execute the given callback while selecting the given columns.
*
* After running the callback, the columns are reset to the original value.
*
* @param array $columns
* @param Closure $callback
* @return mixed
*/
protected
function
onceWithColumns
(
$columns
,
Closure
$callback
)
{
$original
=
$this
->
columns
;
if
(
is_null
(
$original
))
{
$this
->
columns
=
$columns
;
}
$result
=
$callback
();
$this
->
columns
=
$original
;
return
$result
;
}
/**
* Compiles and returns the current query string.
*
* @return string
*/
public
function
getQuery
()
{
// We need to ensure we have at least one filter, as
// no query results will be returned otherwise.
if
(
count
(
array_filter
(
$this
->
filters
))
===
0
)
{
$this
->
whereHas
(
'objectclass'
);
}
return
$this
->
grammar
->
compile
(
$this
);
}
/**
* Returns the unescaped query.
*
* @return string
*/
public
function
getUnescapedQuery
()
{
return
Utilities
::
unescape
(
$this
->
getQuery
());
}
/**
* Returns the current Grammar instance.
*
* @return Grammar
*/
public
function
getGrammar
()
{
return
$this
->
grammar
;
}
/**
* Returns the current Cache instance.
*
* @return Cache|null
*/
public
function
getCache
()
{
return
$this
->
cache
;
}
/**
* Returns the current Connection instance.
*
* @return Connection
*/
public
function
getConnection
()
{
return
$this
->
connection
;
}
/**
* Returns the query type.
*
* @return string
*/
public
function
getType
()
{
return
$this
->
type
;
}
/**
* Set the base distinguished name of the query.
*
* @param Model|string $dn
* @return $this
*/
public
function
setBaseDn
(
$dn
)
{
$this
->
baseDn
=
$this
->
substituteBaseInDn
(
$dn
);
return
$this
;
}
/**
* Get the base distinguished name of the query.
*
* @return string|null
*/
public
function
getBaseDn
()
{
return
$this
->
baseDn
;
}
/**
* Get the distinguished name of the query.
*
* @return string
*/
public
function
getDn
()
{
return
$this
->
dn
;
}
/**
* Set the distinguished name for the query.
*
* @param string|Model|null $dn
* @return $this
*/
public
function
setDn
(
$dn
=
null
)
{
$this
->
dn
=
$this
->
substituteBaseInDn
(
$dn
);
return
$this
;
}
/**
* Substitute the base DN string template for the current base.
*
* @param Model|string $dn
* @return string
*/
protected
function
substituteBaseInDn
(
$dn
)
{
return
str_replace
(
'{base}'
,
$this
->
baseDn
?:
''
,
(
string
)
(
$dn
instanceof
Model
?
$dn
->
getDn
()
:
$dn
)
);
}
/**
* Alias for setting the distinguished name for the query.
*
* @param string|Model|null $dn
* @return $this
*/
public
function
in
(
$dn
=
null
)
{
return
$this
->
setDn
(
$dn
);
}
/**
* Set the size limit of the current query.
*
* @param int $limit
* @return $this
*/
public
function
limit
(
$limit
=
0
)
{
$this
->
limit
=
$limit
;
return
$this
;
}
/**
* Returns a new query for the given model.
*
* @param Model $model
* @return ModelBuilder
*/
public
function
model
(
Model
$model
)
{
return
$model
->
newQueryBuilder
(
$this
->
connection
)
->
setCache
(
$this
->
connection
->
getCache
())
->
setBaseDn
(
$this
->
baseDn
)
->
setModel
(
$model
);
}
/**
* Performs the specified query on the current LDAP connection.
*
* @param string $query
* @return Collection|array
*/
public
function
query
(
$query
)
{
$start
=
microtime
(
true
);
// Here we will create the execution callback. This allows us
// to only execute an LDAP request if caching is disabled
// or if no cache of the given query exists yet.
$callback
=
function
()
use
(
$query
)
{
return
$this
->
parse
(
$this
->
run
(
$query
));
};
$results
=
$this
->
getCachedResponse
(
$query
,
$callback
);
$this
->
logQuery
(
$this
,
$this
->
type
,
$this
->
getElapsedTime
(
$start
));
return
$this
->
process
(
$results
);
}
/**
* Paginates the current LDAP query.
*
* @param int $pageSize
* @param bool $isCritical
* @return Collection|array
*/
public
function
paginate
(
$pageSize
=
1000
,
$isCritical
=
false
)
{
$this
->
paginated
=
true
;
$start
=
microtime
(
true
);
$query
=
$this
->
getQuery
();
// Here we will create the pagination callback. This allows us
// to only execute an LDAP request if caching is disabled
// or if no cache of the given query exists yet.
$callback
=
function
()
use
(
$query
,
$pageSize
,
$isCritical
)
{
return
$this
->
runPaginate
(
$query
,
$pageSize
,
$isCritical
);
};
$pages
=
$this
->
getCachedResponse
(
$query
,
$callback
);
$this
->
logQuery
(
$this
,
'paginate'
,
$this
->
getElapsedTime
(
$start
));
return
$this
->
process
(
$pages
);
}
/**
* Runs the paginate operation with the given filter.
*
* @param string $filter
* @param int $perPage
* @param bool $isCritical
* @return array
*/
protected
function
runPaginate
(
$filter
,
$perPage
,
$isCritical
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$filter
,
$perPage
,
$isCritical
)
{
return
(
new
Paginator
(
$this
,
$filter
,
$perPage
,
$isCritical
))->
execute
(
$ldap
);
});
}
/**
* Execute a callback over each item while chunking.
*
* @param Closure $callback
* @param int $pageSize
* @param bool $isCritical
* @param bool $isolate
* @return bool
*/
public
function
each
(
Closure
$callback
,
$pageSize
=
1000
,
$isCritical
=
false
,
$isolate
=
false
)
{
return
$this
->
chunk
(
$pageSize
,
function
(
$results
)
use
(
$callback
)
{
foreach
(
$results
as
$key
=>
$value
)
{
if
(
$callback
(
$value
,
$key
)
===
false
)
{
return
false
;
}
}
},
$isCritical
,
$isolate
);
}
/**
* Chunk the results of a paginated LDAP query.
*
* @param int $pageSize
* @param Closure $callback
* @param bool $isCritical
* @param bool $isolate
* @return bool
*/
public
function
chunk
(
$pageSize
,
Closure
$callback
,
$isCritical
=
false
,
$isolate
=
false
)
{
$start
=
microtime
(
true
);
$chunk
=
function
(
Builder
$query
)
use
(
$pageSize
,
$callback
,
$isCritical
)
{
$page
=
1
;
foreach
(
$query
->
runChunk
(
$this
->
getQuery
(),
$pageSize
,
$isCritical
)
as
$chunk
)
{
if
(
$callback
(
$this
->
process
(
$chunk
),
$page
)
===
false
)
{
return
false
;
}
$page
++;
}
};
$isolate
?
$this
->
connection
->
isolate
(
function
(
Connection
$replicate
)
use
(
$chunk
)
{
$chunk
(
$this
->
clone
()->
setConnection
(
$replicate
));
})
:
$chunk
(
$this
);
$this
->
logQuery
(
$this
,
'chunk'
,
$this
->
getElapsedTime
(
$start
));
return
true
;
}
/**
* Runs the chunk operation with the given filter.
*
* @param string $filter
* @param int $perPage
* @param bool $isCritical
* @return \Generator
*/
protected
function
runChunk
(
$filter
,
$perPage
,
$isCritical
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$filter
,
$perPage
,
$isCritical
)
{
return
(
new
LazyPaginator
(
$this
,
$filter
,
$perPage
,
$isCritical
))->
execute
(
$ldap
);
});
}
/**
* Create a slice of the LDAP query into a page.
*
* @param int $page
* @param int $perPage
* @param string $orderBy
* @param string $orderByDir
* @return Slice
*/
public
function
slice
(
$page
=
1
,
$perPage
=
100
,
$orderBy
=
'cn'
,
$orderByDir
=
'asc'
)
{
$results
=
$this
->
forPage
(
$page
,
$perPage
,
$orderBy
,
$orderByDir
);
$total
=
$this
->
controlsResponse
[
LDAP_CONTROL_VLVRESPONSE
][
'value'
][
'count'
]
??
0
;
// Some LDAP servers seem to have an issue where the last result in a virtual
// list view will always be returned, regardless of the offset being larger
// than the result itself. In this case, we will manually return an empty
// response so that no objects are deceivingly included in the slice.
$objects
=
$page
>
max
((
int
)
ceil
(
$total
/
$perPage
),
1
)
?
(
$this
instanceof
ModelBuilder
?
$this
->
model
->
newCollection
()
:
[])
:
$results
;
return
new
Slice
(
$objects
,
$total
,
$perPage
,
$page
);
}
/**
* Get the results of a query for a given page.
*
* @param int $page
* @param int $perPage
* @param string $orderBy
* @param string $orderByDir
* @return Collection|array
*/
public
function
forPage
(
$page
=
1
,
$perPage
=
100
,
$orderBy
=
'cn'
,
$orderByDir
=
'asc'
)
{
if
(!
$this
->
hasOrderBy
())
{
$this
->
orderBy
(
$orderBy
,
$orderByDir
);
}
$this
->
addControl
(
LDAP_CONTROL_VLVREQUEST
,
true
,
[
'before'
=>
0
,
'after'
=>
$perPage
-
1
,
'offset'
=>
(
$page
*
$perPage
)
-
$perPage
+
1
,
'count'
=>
0
,
]);
return
$this
->
get
();
}
/**
* Processes and converts the given LDAP results into models.
*
* @param array $results
* @return array
*/
protected
function
process
(
array
$results
)
{
unset
(
$results
[
'count'
]);
if
(
$this
->
paginated
)
{
return
$this
->
flattenPages
(
$results
);
}
return
$results
;
}
/**
* Flattens LDAP paged results into a single array.
*
* @param array $pages
* @return array
*/
protected
function
flattenPages
(
array
$pages
)
{
$records
=
[];
foreach
(
$pages
as
$page
)
{
unset
(
$page
[
'count'
]);
$records
=
array_merge
(
$records
,
$page
);
}
return
$records
;
}
/**
* Get the cached response or execute and cache the callback value.
*
* @param string $query
* @param Closure $callback
* @return mixed
*/
protected
function
getCachedResponse
(
$query
,
Closure
$callback
)
{
if
(
$this
->
cache
&&
$this
->
caching
)
{
$key
=
$this
->
getCacheKey
(
$query
);
if
(
$this
->
flushCache
)
{
$this
->
cache
->
delete
(
$key
);
}
return
$this
->
cache
->
remember
(
$key
,
$this
->
cacheUntil
,
$callback
);
}
return
$callback
();
}
/**
* Runs the query operation with the given filter.
*
* @param string $filter
* @return resource
*/
public
function
run
(
$filter
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$filter
)
{
// We will avoid setting the controls during any pagination
// requests as it will clear the cookie we need to send
// to the server upon retrieving every page.
if
(!
$this
->
paginated
)
{
// Before running the query, we will set the LDAP server controls. This
// allows the controls to be automatically reset upon each new query
// that is conducted on the same connection during each request.
$ldap
->
setOption
(
LDAP_OPT_SERVER_CONTROLS
,
$this
->
controls
);
}
return
$ldap
->{
$this
->
type
}(
$this
->
dn
??
$this
->
baseDn
,
$filter
,
$this
->
getSelects
(),
$onlyAttributes
=
false
,
$this
->
limit
);
});
}
/**
* Parses the given LDAP resource by retrieving its entries.
*
* @param resource $resource
* @return array
*/
public
function
parse
(
$resource
)
{
if
(!
$resource
)
{
return
[];
}
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$resource
)
{
$this
->
controlsResponse
=
$this
->
controls
;
$errorCode
=
0
;
$dn
=
$errorMessage
=
$refs
=
null
;
// Process the server controls response.
$ldap
->
parseResult
(
$resource
,
$errorCode
,
$dn
,
$errorMessage
,
$refs
,
$this
->
controlsResponse
);
$entries
=
$ldap
->
getEntries
(
$resource
);
// Free up memory.
if
(
is_resource
(
$resource
)
||
$resource
instanceof
Result
)
{
$ldap
->
freeResult
(
$resource
);
}
return
$entries
;
});
}
/**
* Returns the cache key.
*
* @param string $query
* @return string
*/
protected
function
getCacheKey
(
$query
)
{
$host
=
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
{
return
$ldap
->
getHost
();
});
$key
=
$host
.
$this
->
type
.
$this
->
getDn
()
.
$query
.
implode
(
$this
->
getSelects
())
.
$this
->
limit
.
$this
->
paginated
;
return
md5
(
$key
);
}
/**
* Returns the first entry in a search result.
*
* @param array|string $columns
* @return Model|null
*/
public
function
first
(
$columns
=
[
'*'
])
{
return
Arr
::
first
(
$this
->
limit
(
1
)->
get
(
$columns
)
);
}
/**
* Returns the first entry in a search result.
*
* If no entry is found, an exception is thrown.
*
* @param array|string $columns
* @return Model|array
*
* @throws ObjectNotFoundException
*/
public
function
firstOrFail
(
$columns
=
[
'*'
])
{
if
(!
$record
=
$this
->
first
(
$columns
))
{
$this
->
throwNotFoundException
(
$this
->
getUnescapedQuery
(),
$this
->
dn
);
}
return
$record
;
}
/**
* Return the first entry in a result, or execute the callback.
*
* @param Closure $callback
* @return Model|mixed
*/
public
function
firstOr
(
Closure
$callback
)
{
return
$this
->
first
()
?:
$callback
();
}
/**
* Execute the query and get the first result if it's the sole matching record.
*
* @param array|string $columns
* @return Model|array
*
* @throws ObjectsNotFoundException
* @throws MultipleObjectsFoundException
*/
public
function
sole
(
$columns
=
[
'*'
])
{
$result
=
$this
->
limit
(
2
)->
get
(
$columns
);
if
(
empty
(
$result
))
{
throw
new
ObjectsNotFoundException
;
}
if
(
count
(
$result
)
>
1
)
{
throw
new
MultipleObjectsFoundException
;
}
return
reset
(
$result
);
}
/**
* Determine if any results exist for the current query.
*
* @return bool
*/
public
function
exists
()
{
return
!
is_null
(
$this
->
first
());
}
/**
* Determine if no results exist for the current query.
*
* @return bool
*/
public
function
doesntExist
()
{
return
!
$this
->
exists
();
}
/**
* Execute the given callback if no rows exist for the current query.
*
* @param Closure $callback
* @return bool|mixed
*/
public
function
existsOr
(
Closure
$callback
)
{
return
$this
->
exists
()
?
true
:
$callback
();
}
/**
* Throws a not found exception.
*
* @param string $query
* @param string $dn
*
* @throws ObjectNotFoundException
*/
protected
function
throwNotFoundException
(
$query
,
$dn
)
{
throw
ObjectNotFoundException
::
forQuery
(
$query
,
$dn
);
}
/**
* Finds a record by the specified attribute and value.
*
* @param string $attribute
* @param string $value
* @param array|string $columns
* @return Model|static|null
*/
public
function
findBy
(
$attribute
,
$value
,
$columns
=
[
'*'
])
{
try
{
return
$this
->
findByOrFail
(
$attribute
,
$value
,
$columns
);
}
catch
(
ObjectNotFoundException
$e
)
{
return
;
}
}
/**
* Finds a record by the specified attribute and value.
*
* If no record is found an exception is thrown.
*
* @param string $attribute
* @param string $value
* @param array|string $columns
* @return Model
*
* @throws ObjectNotFoundException
*/
public
function
findByOrFail
(
$attribute
,
$value
,
$columns
=
[
'*'
])
{
return
$this
->
whereEquals
(
$attribute
,
$value
)->
firstOrFail
(
$columns
);
}
/**
* Find many records by distinguished name.
*
* @param string|array $dns
* @param array $columns
* @return array|Collection
*/
public
function
findMany
(
$dns
,
$columns
=
[
'*'
])
{
if
(
empty
(
$dns
))
{
return
$this
->
process
([]);
}
$objects
=
[];
foreach
((
array
)
$dns
as
$dn
)
{
if
(!
is_null
(
$object
=
$this
->
find
(
$dn
,
$columns
)))
{
$objects
[]
=
$object
;
}
}
return
$this
->
process
(
$objects
);
}
/**
* Finds many records by the specified attribute.
*
* @param string $attribute
* @param array $values
* @param array $columns
* @return Collection
*/
public
function
findManyBy
(
$attribute
,
array
$values
=
[],
$columns
=
[
'*'
])
{
$query
=
$this
->
select
(
$columns
);
foreach
(
$values
as
$value
)
{
$query
->
orWhere
([
$attribute
=>
$value
]);
}
return
$query
->
get
();
}
/**
* Finds a record by its distinguished name.
*
* @param string|array $dn
* @param array|string $columns
* @return Model|static|array|Collection|null
*/
public
function
find
(
$dn
,
$columns
=
[
'*'
])
{
if
(
is_array
(
$dn
))
{
return
$this
->
findMany
(
$dn
,
$columns
);
}
try
{
return
$this
->
findOrFail
(
$dn
,
$columns
);
}
catch
(
ObjectNotFoundException
$e
)
{
return
;
}
}
/**
* Finds a record by its distinguished name.
*
* Fails upon no records returned.
*
* @param string $dn
* @param array|string $columns
* @return Model|static
*
* @throws ObjectNotFoundException
*/
public
function
findOrFail
(
$dn
,
$columns
=
[
'*'
])
{
return
$this
->
setDn
(
$dn
)
->
read
()
->
whereHas
(
'objectclass'
)
->
firstOrFail
(
$columns
);
}
/**
* Adds the inserted fields to query on the current LDAP connection.
*
* @param array|string $columns
* @return $this
*/
public
function
select
(
$columns
=
[
'*'
])
{
$columns
=
is_array
(
$columns
)
?
$columns
:
func_get_args
();
if
(!
empty
(
$columns
))
{
$this
->
columns
=
$columns
;
}
return
$this
;
}
/**
* Add a new select column to the query.
*
* @param array|mixed $column
* @return $this
*/
public
function
addSelect
(
$column
)
{
$column
=
is_array
(
$column
)
?
$column
:
func_get_args
();
$this
->
columns
=
array_merge
((
array
)
$this
->
columns
,
$column
);
return
$this
;
}
/**
* Add an order by control to the query.
*
* @param string $attribute
* @param string $direction
* @return $this
*/
public
function
orderBy
(
$attribute
,
$direction
=
'asc'
)
{
return
$this
->
addControl
(
LDAP_CONTROL_SORTREQUEST
,
true
,
[
[
'attr'
=>
$attribute
,
'reverse'
=>
$direction
===
'desc'
],
]);
}
/**
* Add an order by descending control to the query.
*
* @param string $attribute
* @return $this
*/
public
function
orderByDesc
(
$attribute
)
{
return
$this
->
orderBy
(
$attribute
,
'desc'
);
}
/**
* Determine if the query has a sotr request control header.
*
* @return bool
*/
public
function
hasOrderBy
()
{
return
$this
->
hasControl
(
LDAP_CONTROL_SORTREQUEST
);
}
/**
* Adds a raw filter to the current query.
*
* @param array|string $filters
* @return $this
*/
public
function
rawFilter
(
$filters
=
[])
{
$filters
=
is_array
(
$filters
)
?
$filters
:
func_get_args
();
foreach
(
$filters
as
$filter
)
{
$this
->
filters
[
'raw'
][]
=
$filter
;
}
return
$this
;
}
/**
* Adds a nested 'and' filter to the current query.
*
* @param Closure $closure
* @return $this
*/
public
function
andFilter
(
Closure
$closure
)
{
$query
=
$this
->
newNestedInstance
(
$closure
);
return
$this
->
rawFilter
(
$this
->
grammar
->
compileAnd
(
$query
->
getQuery
())
);
}
/**
* Adds a nested 'or' filter to the current query.
*
* @param Closure $closure
* @return $this
*/
public
function
orFilter
(
Closure
$closure
)
{
$query
=
$this
->
newNestedInstance
(
$closure
);
return
$this
->
rawFilter
(
$this
->
grammar
->
compileOr
(
$query
->
getQuery
())
);
}
/**
* Adds a nested 'not' filter to the current query.
*
* @param Closure $closure
* @return $this
*/
public
function
notFilter
(
Closure
$closure
)
{
$query
=
$this
->
newNestedInstance
(
$closure
);
return
$this
->
rawFilter
(
$this
->
grammar
->
compileNot
(
$query
->
getQuery
())
);
}
/**
* Adds a where clause to the current query.
*
* @param string|array $field
* @param string $operator
* @param string $value
* @param string $boolean
* @param bool $raw
* @return $this
*
* @throws InvalidArgumentException
*/
public
function
where
(
$field
,
$operator
=
null
,
$value
=
null
,
$boolean
=
'and'
,
$raw
=
false
)
{
if
(
is_array
(
$field
))
{
// If the field is an array, we will assume we have been
// provided with an array of key-value pairs and can
// add them each as their own seperate where clause.
return
$this
->
addArrayOfWheres
(
$field
,
$boolean
,
$raw
);
}
// If we have been provided with two arguments not a "has" or
// "not has" operator, we'll assume the developer is creating
// an "equals" clause and set the proper operator in place.
if
(
func_num_args
()
===
2
&&
!
in_array
(
$operator
,
[
'*'
,
'!*'
]))
{
[
$value
,
$operator
]
=
[
$operator
,
'='
];
}
if
(!
in_array
(
$operator
,
$this
->
grammar
->
getOperators
()))
{
throw
new
InvalidArgumentException
(
"Invalid LDAP filter operator [$operator]"
);
}
// We'll escape the value if raw isn't requested.
$value
=
$this
->
prepareWhereValue
(
$field
,
$value
,
$raw
);
$field
=
$this
->
escape
(
$field
)->
both
()->
get
();
$this
->
addFilter
(
$boolean
,
compact
(
'field'
,
'operator'
,
'value'
));
return
$this
;
}
/**
* Prepare the value for being queried.
*
* @param string $field
* @param string $value
* @param bool $raw
* @return string
*/
protected
function
prepareWhereValue
(
$field
,
$value
,
$raw
=
false
)
{
return
$raw
?
$value
:
$this
->
escape
(
$value
);
}
/**
* Adds a raw where clause to the current query.
*
* Values given to this method are not escaped.
*
* @param string|array $field
* @param string $operator
* @param string $value
* @return $this
*/
public
function
whereRaw
(
$field
,
$operator
=
null
,
$value
=
null
)
{
return
$this
->
where
(
$field
,
$operator
,
$value
,
'and'
,
true
);
}
/**
* Adds a 'where equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereEquals
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'='
,
$value
);
}
/**
* Adds a 'where not equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereNotEquals
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'!'
,
$value
);
}
/**
* Adds a 'where approximately equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereApproximatelyEquals
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'~='
,
$value
);
}
/**
* Adds a 'where has' clause to the current query.
*
* @param string $field
* @return $this
*/
public
function
whereHas
(
$field
)
{
return
$this
->
where
(
$field
,
'*'
);
}
/**
* Adds a 'where not has' clause to the current query.
*
* @param string $field
* @return $this
*/
public
function
whereNotHas
(
$field
)
{
return
$this
->
where
(
$field
,
'!*'
);
}
/**
* Adds a 'where contains' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereContains
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'contains'
,
$value
);
}
/**
* Adds a 'where contains' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereNotContains
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'not_contains'
,
$value
);
}
/**
* Query for entries that match any of the values provided for the given field.
*
* @param string $field
* @param array $values
* @return $this
*/
public
function
whereIn
(
$field
,
array
$values
)
{
return
$this
->
orFilter
(
function
(
self
$query
)
use
(
$field
,
$values
)
{
foreach
(
$values
as
$value
)
{
$query
->
whereEquals
(
$field
,
$value
);
}
});
}
/**
* Adds a 'between' clause to the current query.
*
* @param string $field
* @param array $values
* @return $this
*/
public
function
whereBetween
(
$field
,
array
$values
)
{
return
$this
->
where
([
[
$field
,
'>='
,
$values
[
0
]],
[
$field
,
'<='
,
$values
[
1
]],
]);
}
/**
* Adds a 'where starts with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereStartsWith
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'starts_with'
,
$value
);
}
/**
* Adds a 'where *not* starts with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereNotStartsWith
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'not_starts_with'
,
$value
);
}
/**
* Adds a 'where ends with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereEndsWith
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'ends_with'
,
$value
);
}
/**
* Adds a 'where *not* ends with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
whereNotEndsWith
(
$field
,
$value
)
{
return
$this
->
where
(
$field
,
'not_ends_with'
,
$value
);
}
/**
* Only include deleted models in the results.
*
* @return $this
*/
public
function
whereDeleted
()
{
return
$this
->
withDeleted
()->
whereEquals
(
'isDeleted'
,
'TRUE'
);
}
/**
* Set the LDAP control option to include deleted LDAP models.
*
* @return $this
*/
public
function
withDeleted
()
{
return
$this
->
addControl
(
LdapInterface
::
OID_SERVER_SHOW_DELETED
,
$isCritical
=
true
);
}
/**
* Add a server control to the query.
*
* @param string $oid
* @param bool $isCritical
* @param mixed $value
* @return $this
*/
public
function
addControl
(
$oid
,
$isCritical
=
false
,
$value
=
null
)
{
$this
->
controls
[
$oid
]
=
compact
(
'oid'
,
'isCritical'
,
'value'
);
return
$this
;
}
/**
* Determine if the server control exists on the query.
*
* @param string $oid
* @return bool
*/
public
function
hasControl
(
$oid
)
{
return
array_key_exists
(
$oid
,
$this
->
controls
);
}
/**
* Adds an 'or where' clause to the current query.
*
* @param array|string $field
* @param string|null $operator
* @param string|null $value
* @return $this
*/
public
function
orWhere
(
$field
,
$operator
=
null
,
$value
=
null
)
{
return
$this
->
where
(
$field
,
$operator
,
$value
,
'or'
);
}
/**
* Adds a raw or where clause to the current query.
*
* Values given to this method are not escaped.
*
* @param string $field
* @param string $operator
* @param string $value
* @return $this
*/
public
function
orWhereRaw
(
$field
,
$operator
=
null
,
$value
=
null
)
{
return
$this
->
where
(
$field
,
$operator
,
$value
,
'or'
,
true
);
}
/**
* Adds an 'or where has' clause to the current query.
*
* @param string $field
* @return $this
*/
public
function
orWhereHas
(
$field
)
{
return
$this
->
orWhere
(
$field
,
'*'
);
}
/**
* Adds a 'where not has' clause to the current query.
*
* @param string $field
* @return $this
*/
public
function
orWhereNotHas
(
$field
)
{
return
$this
->
orWhere
(
$field
,
'!*'
);
}
/**
* Adds an 'or where equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereEquals
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'='
,
$value
);
}
/**
* Adds an 'or where not equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereNotEquals
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'!'
,
$value
);
}
/**
* Adds a 'or where approximately equals' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereApproximatelyEquals
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'~='
,
$value
);
}
/**
* Adds an 'or where contains' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereContains
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'contains'
,
$value
);
}
/**
* Adds an 'or where *not* contains' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereNotContains
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'not_contains'
,
$value
);
}
/**
* Adds an 'or where starts with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereStartsWith
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'starts_with'
,
$value
);
}
/**
* Adds an 'or where *not* starts with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereNotStartsWith
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'not_starts_with'
,
$value
);
}
/**
* Adds an 'or where ends with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereEndsWith
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'ends_with'
,
$value
);
}
/**
* Adds an 'or where *not* ends with' clause to the current query.
*
* @param string $field
* @param string $value
* @return $this
*/
public
function
orWhereNotEndsWith
(
$field
,
$value
)
{
return
$this
->
orWhere
(
$field
,
'not_ends_with'
,
$value
);
}
/**
* Adds a filter binding onto the current query.
*
* @param string $type The type of filter to add.
* @param array $bindings The bindings of the filter.
* @return $this
*
* @throws InvalidArgumentException
*/
public
function
addFilter
(
$type
,
array
$bindings
)
{
if
(!
array_key_exists
(
$type
,
$this
->
filters
))
{
throw
new
InvalidArgumentException
(
"Filter type: [$type] is invalid."
);
}
// Each filter clause require key bindings to be set. We
// will validate this here to ensure all of them have
// been provided, or throw an exception otherwise.
if
(
$missing
=
$this
->
missingBindingKeys
(
$bindings
))
{
$keys
=
implode
(
', '
,
$missing
);
throw
new
InvalidArgumentException
(
"Invalid filter bindings. Missing: [$keys] keys."
);
}
$this
->
filters
[
$type
][]
=
$bindings
;
return
$this
;
}
/**
* Extract any missing required binding keys.
*
* @param array $bindings
* @return array
*/
protected
function
missingBindingKeys
(
$bindings
)
{
$required
=
array_flip
([
'field'
,
'operator'
,
'value'
]);
$existing
=
array_intersect_key
(
$required
,
$bindings
);
return
array_keys
(
array_diff_key
(
$required
,
$existing
));
}
/**
* Get all the filters on the query.
*
* @return array
*/
public
function
getFilters
()
{
return
$this
->
filters
;
}
/**
* Clear the query filters.
*
* @return $this
*/
public
function
clearFilters
()
{
foreach
(
array_keys
(
$this
->
filters
)
as
$type
)
{
$this
->
filters
[
$type
]
=
[];
}
return
$this
;
}
/**
* Determine if the query has attributes selected.
*
* @return bool
*/
public
function
hasSelects
()
{
return
count
(
$this
->
columns
)
>
0
;
}
/**
* Get the attributes to select on the search.
*
* @return array
*/
public
function
getSelects
()
{
$selects
=
$this
->
columns
??
[
'*'
];
if
(
in_array
(
'*'
,
$selects
))
{
return
$selects
;
}
if
(
in_array
(
'objectclass'
,
$selects
))
{
return
$selects
;
}
// If the * character is not provided in the selected columns,
// we need to ensure we always select the object class, as
// this is used for constructing models properly.
$selects
[]
=
'objectclass'
;
return
$selects
;
}
/**
* Set the query to search on the base distinguished name.
*
* This will result in one record being returned.
*
* @return $this
*/
public
function
read
()
{
$this
->
type
=
'read'
;
return
$this
;
}
/**
* Set the query to search one level on the base distinguished name.
*
* @return $this
*/
public
function
listing
()
{
$this
->
type
=
'listing'
;
return
$this
;
}
/**
* Set the query to search the entire directory on the base distinguished name.
*
* @return $this
*/
public
function
recursive
()
{
$this
->
type
=
'search'
;
return
$this
;
}
/**
* Whether to mark the current query as nested.
*
* @param bool $nested
* @return $this
*/
public
function
nested
(
$nested
=
true
)
{
$this
->
nested
=
(
bool
)
$nested
;
return
$this
;
}
/**
* Enables caching on the current query until the given date.
*
* If flushing is enabled, the query cache will be flushed and then re-cached.
*
* @param DateTimeInterface $until When to expire the query cache.
* @param bool $flush Whether to force-flush the query cache.
* @return $this
*/
public
function
cache
(
DateTimeInterface
$until
=
null
,
$flush
=
false
)
{
$this
->
caching
=
true
;
$this
->
cacheUntil
=
$until
;
$this
->
flushCache
=
$flush
;
return
$this
;
}
/**
* Determine if the query is nested.
*
* @return bool
*/
public
function
isNested
()
{
return
$this
->
nested
===
true
;
}
/**
* Determine whether the query is paginated.
*
* @return bool
*/
public
function
isPaginated
()
{
return
$this
->
paginated
;
}
/**
* Insert an entry into the directory.
*
* @param string $dn
* @param array $attributes
* @return bool
*
* @throws LdapRecordException
*/
public
function
insert
(
$dn
,
array
$attributes
)
{
if
(
empty
(
$dn
))
{
throw
new
LdapRecordException
(
'A new LDAP object must have a distinguished name (dn).'
);
}
if
(!
array_key_exists
(
'objectclass'
,
$attributes
))
{
throw
new
LdapRecordException
(
'A new LDAP object must contain at least one object class (objectclass) to be created.'
);
}
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$attributes
)
{
return
$ldap
->
add
(
$dn
,
$attributes
);
});
}
/**
* Create attributes on the entry in the directory.
*
* @param string $dn
* @param array $attributes
* @return bool
*/
public
function
insertAttributes
(
$dn
,
array
$attributes
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$attributes
)
{
return
$ldap
->
modAdd
(
$dn
,
$attributes
);
});
}
/**
* Update the entry with the given modifications.
*
* @param string $dn
* @param array $modifications
* @return bool
*/
public
function
update
(
$dn
,
array
$modifications
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$modifications
)
{
return
$ldap
->
modifyBatch
(
$dn
,
$modifications
);
});
}
/**
* Update an entries attribute in the directory.
*
* @param string $dn
* @param array $attributes
* @return bool
*/
public
function
updateAttributes
(
$dn
,
array
$attributes
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$attributes
)
{
return
$ldap
->
modReplace
(
$dn
,
$attributes
);
});
}
/**
* Delete an entry from the directory.
*
* @param string $dn
* @return bool
*/
public
function
delete
(
$dn
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
)
{
return
$ldap
->
delete
(
$dn
);
});
}
/**
* Delete attributes on the entry in the directory.
*
* @param string $dn
* @param array $attributes
* @return bool
*/
public
function
deleteAttributes
(
$dn
,
array
$attributes
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$attributes
)
{
return
$ldap
->
modDelete
(
$dn
,
$attributes
);
});
}
/**
* Rename an entry in the directory.
*
* @param string $dn
* @param string $rdn
* @param string $newParentDn
* @param bool $deleteOldRdn
* @return bool
*/
public
function
rename
(
$dn
,
$rdn
,
$newParentDn
,
$deleteOldRdn
=
true
)
{
return
$this
->
connection
->
run
(
function
(
LdapInterface
$ldap
)
use
(
$dn
,
$rdn
,
$newParentDn
,
$deleteOldRdn
)
{
return
$ldap
->
rename
(
$dn
,
$rdn
,
$newParentDn
,
$deleteOldRdn
);
});
}
/**
* Clone the query.
*
* @return static
*/
public
function
clone
()
{
return
clone
$this
;
}
/**
* Handle dynamic method calls on the query builder.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws BadMethodCallException
*/
public
function
__call
(
$method
,
$parameters
)
{
// If the beginning of the method being called contains
// 'where', we will assume a dynamic 'where' clause is
// being performed and pass the parameters to it.
if
(
substr
(
$method
,
0
,
5
)
===
'where'
)
{
return
$this
->
dynamicWhere
(
$method
,
$parameters
);
}
throw
new
BadMethodCallException
(
sprintf
(
'Call to undefined method %s::%s()'
,
static
::
class
,
$method
));
}
/**
* Handles dynamic "where" clauses to the query.
*
* @param string $method
* @param array $parameters
* @return $this
*/
public
function
dynamicWhere
(
$method
,
$parameters
)
{
$finder
=
substr
(
$method
,
5
);
$segments
=
preg_split
(
'/(And|Or)(?=[A-Z])/'
,
$finder
,
-
1
,
PREG_SPLIT_DELIM_CAPTURE
);
// The connector variable will determine which connector will be used for the
// query condition. We will change it as we come across new boolean values
// in the dynamic method strings, which could contain a number of these.
$connector
=
'and'
;
$index
=
0
;
foreach
(
$segments
as
$segment
)
{
// If the segment is not a boolean connector, we can assume it is a column's name
// and we will add it to the query as a new constraint as a where clause, then
// we can keep iterating through the dynamic method string's segments again.
if
(
$segment
!=
'And'
&&
$segment
!=
'Or'
)
{
$this
->
addDynamic
(
$segment
,
$connector
,
$parameters
,
$index
);
$index
++;
}
// Otherwise, we will store the connector so we know how the next where clause we
// find in the query should be connected to the previous ones, meaning we will
// have the proper boolean connector to connect the next where clause found.
else
{
$connector
=
$segment
;
}
}
return
$this
;
}
/**
* Adds an array of wheres to the current query.
*
* @param array $wheres
* @param string $boolean
* @param bool $raw
* @return $this
*/
protected
function
addArrayOfWheres
(
$wheres
,
$boolean
,
$raw
)
{
foreach
(
$wheres
as
$key
=>
$value
)
{
if
(
is_numeric
(
$key
)
&&
is_array
(
$value
))
{
// If the key is numeric and the value is an array, we'll
// assume we've been given an array with conditionals.
[
$field
,
$condition
]
=
$value
;
// Since a value is optional for some conditionals, we will
// try and retrieve the third parameter from the array,
// but is entirely optional.
$value
=
Arr
::
get
(
$value
,
2
);
$this
->
where
(
$field
,
$condition
,
$value
,
$boolean
);
}
else
{
// If the value is not an array, we will assume an equals clause.
$this
->
where
(
$key
,
'='
,
$value
,
$boolean
,
$raw
);
}
}
return
$this
;
}
/**
* Add a single dynamic where clause statement to the query.
*
* @param string $segment
* @param string $connector
* @param array $parameters
* @param int $index
* @return void
*/
protected
function
addDynamic
(
$segment
,
$connector
,
$parameters
,
$index
)
{
// If no parameters were given to the dynamic where clause,
// we can assume a "has" attribute filter is being added.
if
(
count
(
$parameters
)
===
0
)
{
$this
->
where
(
strtolower
(
$segment
),
'*'
,
null
,
strtolower
(
$connector
));
}
else
{
$this
->
where
(
strtolower
(
$segment
),
'='
,
$parameters
[
$index
],
strtolower
(
$connector
));
}
}
/**
* Logs the given executed query information by firing its query event.
*
* @param Builder $query
* @param string $type
* @param null|float $time
* @return void
*/
protected
function
logQuery
(
$query
,
$type
,
$time
=
null
)
{
$args
=
[
$query
,
$time
];
switch
(
$type
)
{
case
'listing'
:
$event
=
new
Events\Listing
(...
$args
);
break
;
case
'read'
:
$event
=
new
Events\Read
(...
$args
);
break
;
case
'chunk'
:
$event
=
new
Events\Chunk
(...
$args
);
break
;
case
'paginate'
:
$event
=
new
Events\Paginate
(...
$args
);
break
;
default
:
$event
=
new
Events\Search
(...
$args
);
break
;
}
$this
->
fireQueryEvent
(
$event
);
}
/**
* Fires the given query event.
*
* @param QueryExecuted $event
* @return void
*/
protected
function
fireQueryEvent
(
QueryExecuted
$event
)
{
Container
::
getInstance
()->
getEventDispatcher
()->
fire
(
$event
);
}
/**
* Get the elapsed time since a given starting point.
*
* @param int $start
* @return float
*/
protected
function
getElapsedTime
(
$start
)
{
return
round
((
microtime
(
true
)
-
$start
)
*
1000
,
2
);
}
}
File Metadata
详情
附加的
Mime Type
text/x-php
Expires
9月 12 Fri, 3:11 AM (22 h, 54 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5790
默认替代文本
Builder.php (49 KB)
Attached To
Mode
rMAILCOW mailcow-tracking
附加的
Detach File
Event Timeline
Log In to Comment