blob: 6ba24b454ccab06f7680b94900e424d0fa6d51fa [file] [log] [blame]
<?php
namespace LdapRecord\Models;
use ArrayAccess;
use InvalidArgumentException;
use JsonSerializable;
use LdapRecord\Connection;
use LdapRecord\Container;
use LdapRecord\EscapesValues;
use LdapRecord\Models\Attributes\DistinguishedName;
use LdapRecord\Models\Attributes\Guid;
use LdapRecord\Models\Events\Renamed;
use LdapRecord\Models\Events\Renaming;
use LdapRecord\Query\Model\Builder;
use LdapRecord\Support\Arr;
use UnexpectedValueException;
/** @mixin Builder */
abstract class Model implements ArrayAccess, JsonSerializable
{
use EscapesValues;
use Concerns\HasEvents;
use Concerns\HasScopes;
use Concerns\HasAttributes;
use Concerns\HasGlobalScopes;
use Concerns\HidesAttributes;
use Concerns\HasRelationships;
/**
* Indicates if the model exists in the LDAP directory.
*
* @var bool
*/
public $exists = false;
/**
* Indicates whether the model was created during the current request lifecycle.
*
* @var bool
*/
public $wasRecentlyCreated = false;
/**
* Indicates whether the model was renamed during the current request lifecycle.
*
* @var bool
*/
public $wasRecentlyRenamed = false;
/**
* The models distinguished name.
*
* @var string|null
*/
protected $dn;
/**
* The base DN of where the model should be created in.
*
* @var string|null
*/
protected $in;
/**
* The object classes of the LDAP model.
*
* @var array
*/
public static $objectClasses = [];
/**
* The connection container instance.
*
* @var Container
*/
protected static $container;
/**
* The LDAP connection name for the model.
*
* @var string|null
*/
protected $connection;
/**
* The attribute key that contains the models object GUID.
*
* @var string
*/
protected $guidKey = 'objectguid';
/**
* Contains the models modifications.
*
* @var array
*/
protected $modifications = [];
/**
* The array of global scopes on the model.
*
* @var array
*/
protected static $globalScopes = [];
/**
* The array of booted models.
*
* @var array
*/
protected static $booted = [];
/**
* Constructor.
*
* @param array $attributes
*/
public function __construct(array $attributes = [])
{
$this->bootIfNotBooted();
$this->fill($attributes);
}
/**
* Check if the model needs to be booted and if so, do it.
*
* @return void
*/
protected function bootIfNotBooted()
{
if (! isset(static::$booted[static::class])) {
static::$booted[static::class] = true;
static::boot();
}
}
/**
* The "booting" method of the model.
*
* @return void
*/
protected static function boot()
{
//
}
/**
* Clear the list of booted models so they will be re-booted.
*
* @return void
*/
public static function clearBootedModels()
{
static::$booted = [];
static::$globalScopes = [];
}
/**
* Handle dynamic method calls into the model.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*/
public function __call($method, $parameters)
{
if (method_exists($this, $method)) {
return $this->$method(...$parameters);
}
return $this->newQuery()->$method(...$parameters);
}
/**
* Handle dynamic static method calls into the method.
*
* @param string $method
* @param array $parameters
*
* @return mixed
*/
public static function __callStatic($method, $parameters)
{
return (new static())->$method(...$parameters);
}
/**
* Returns the models distinguished name.
*
* @return string|null
*/
public function getDn()
{
return $this->dn;
}
/**
* Set the models distinguished name.
*
* @param string $dn
*
* @return static
*/
public function setDn($dn)
{
$this->dn = (string) $dn;
return $this;
}
/**
* Get the LDAP connection for the model.
*
* @return Connection
*/
public function getConnection()
{
return static::resolveConnection($this->getConnectionName());
}
/**
* Get the current connection name for the model.
*
* @return string
*/
public function getConnectionName()
{
return $this->connection;
}
/**
* Set the connection associated with the model.
*
* @param string $name
*
* @return $this
*/
public function setConnection($name)
{
$this->connection = $name;
return $this;
}
/**
* Begin querying the model on a given connection.
*
* @param string|null $connection
*
* @return Builder
*/
public static function on($connection = null)
{
$instance = new static();
$instance->setConnection($connection);
return $instance->newQuery();
}
/**
* Get all the models from the directory.
*
* @param array|mixed $attributes
*
* @return Collection|static[]
*/
public static function all($attributes = ['*'])
{
return static::query()->select($attributes)->paginate();
}
/**
* Begin querying the model.
*
* @return Builder
*/
public static function query()
{
return (new static())->newQuery();
}
/**
* Get a new query for builder filtered by the current models object classes.
*
* @return Builder
*/
public function newQuery()
{
return $this->registerModelScopes(
$this->newQueryWithoutScopes()
);
}
/**
* Get a new query builder that doesn't have any global scopes.
*
* @return Builder
*/
public function newQueryWithoutScopes()
{
return static::resolveConnection(
$this->getConnectionName()
)->query()->model($this);
}
/**
* Create a new query builder.
*
* @param Connection $connection
*
* @return Builder
*/
public function newQueryBuilder(Connection $connection)
{
return new Builder($connection);
}
/**
* Create a new model instance.
*
* @param array $attributes
*
* @return static
*/
public function newInstance(array $attributes = [])
{
return (new static($attributes))->setConnection($this->getConnectionName());
}
/**
* Resolve a connection instance.
*
* @param string|null $connection
*
* @return Connection
*/
public static function resolveConnection($connection = null)
{
return static::getConnectionContainer()->get($connection);
}
/**
* Get the connection container.
*
* @return Container
*/
public static function getConnectionContainer()
{
return static::$container ?? static::getDefaultConnectionContainer();
}
/**
* Get the default singleton container instance.
*
* @return Container
*/
public static function getDefaultConnectionContainer()
{
return Container::getInstance();
}
/**
* Set the connection container.
*
* @param Container $container
*
* @return void
*/
public static function setConnectionContainer(Container $container)
{
static::$container = $container;
}
/**
* Unset the connection container.
*
* @return void
*/
public static function unsetConnectionContainer()
{
static::$container = null;
}
/**
* Register the query scopes for this builder instance.
*
* @param Builder $builder
*
* @return Builder
*/
public function registerModelScopes($builder)
{
$this->applyObjectClassScopes($builder);
$this->registerGlobalScopes($builder);
return $builder;
}
/**
* Register the global model scopes.
*
* @param Builder $builder
*
* @return Builder
*/
public function registerGlobalScopes($builder)
{
foreach ($this->getGlobalScopes() as $identifier => $scope) {
$builder->withGlobalScope($identifier, $scope);
}
return $builder;
}
/**
* Apply the model object class scopes to the given builder instance.
*
* @param Builder $query
*
* @return void
*/
public function applyObjectClassScopes(Builder $query)
{
foreach (static::$objectClasses as $objectClass) {
$query->where('objectclass', '=', $objectClass);
}
}
/**
* Returns the models distinguished name when the model is converted to a string.
*
* @return null|string
*/
public function __toString()
{
return $this->getDn();
}
/**
* Returns a new batch modification.
*
* @param string|null $attribute
* @param string|int|null $type
* @param array $values
*
* @return BatchModification
*/
public function newBatchModification($attribute = null, $type = null, $values = [])
{
return new BatchModification($attribute, $type, $values);
}
/**
* Returns a new collection with the specified items.
*
* @param mixed $items
*
* @return Collection
*/
public function newCollection($items = [])
{
return new Collection($items);
}
/**
* Dynamically retrieve attributes on the object.
*
* @param mixed $key
*
* @return bool
*/
public function __get($key)
{
return $this->getAttribute($key);
}
/**
* Dynamically set attributes on the object.
*
* @param mixed $key
* @param mixed $value
*
* @return $this
*/
public function __set($key, $value)
{
return $this->setAttribute($key, $value);
}
/**
* Determine if the given offset exists.
*
* @param string $offset
*
* @return bool
*/
public function offsetExists($offset)
{
return ! is_null($this->getAttribute($offset));
}
/**
* Get the value for a given offset.
*
* @param string $offset
*
* @return mixed
*/
public function offsetGet($offset)
{
return $this->getAttribute($offset);
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
*
* @return void
*/
public function offsetSet($offset, $value)
{
$this->setAttribute($offset, $value);
}
/**
* Unset the value at the given offset.
*
* @param string $offset
*
* @return void
*/
public function offsetUnset($offset)
{
unset($this->attributes[$offset]);
}
/**
* Determine if an attribute exists on the model.
*
* @param string $key
*
* @return bool
*/
public function __isset($key)
{
return $this->offsetExists($key);
}
/**
* Unset an attribute on the model.
*
* @param string $key
*
* @return void
*/
public function __unset($key)
{
$this->offsetUnset($key);
}
/**
* Convert the object into something JSON serializable.
*
* @return array
*/
public function jsonSerialize()
{
return $this->attributesToArray();
}
/**
* Converts extra attributes for JSON serialization.
*
* @param array $attributes
*
* @return array
*/
protected function convertAttributesForJson(array $attributes = [])
{
// If the model has a GUID set, we need to convert
// it due to it being in binary. Otherwise we'll
// receive a JSON serialization exception.
if ($this->hasAttribute($this->guidKey)) {
return array_replace($attributes, [
$this->guidKey => [$this->getConvertedGuid()],
]);
}
return $attributes;
}
/**
* Reload a fresh model instance from the directory.
*
* @return static|false
*/
public function fresh()
{
if (! $this->exists) {
return false;
}
return $this->newQuery()->find($this->dn);
}
/**
* Determine if two models have the same distinguished name and belong to the same connection.
*
* @param static $model
*
* @return bool
*/
public function is(self $model)
{
return $this->dn == $model->getDn() && $this->getConnectionName() == $model->getConnectionName();
}
/**
* Hydrate a new collection of models from LDAP search results.
*
* @param array $records
*
* @return Collection
*/
public function hydrate($records)
{
return $this->newCollection($records)->transform(function ($attributes) {
return $attributes instanceof static
? $attributes
: static::newInstance()->setRawAttributes($attributes);
});
}
/**
* Converts the current model into the given model.
*
* @param Model $into
*
* @return Model
*/
public function convert(self $into)
{
$into->setDn($this->getDn());
$into->setConnection($this->getConnectionName());
$this->exists
? $into->setRawAttributes($this->getAttributes())
: $into->fill($this->getAttributes());
return $into;
}
/**
* Refreshes the current models attributes with the directory values.
*
* @return bool
*/
public function refresh()
{
if ($model = $this->fresh()) {
$this->setRawAttributes($model->getAttributes());
return true;
}
return false;
}
/**
* Get the model's batch modifications to be processed.
*
* @return array
*/
public function getModifications()
{
$builtModifications = [];
foreach ($this->buildModificationsFromDirty() as $modification) {
$builtModifications[] = $modification->get();
}
return array_merge($this->modifications, $builtModifications);
}
/**
* Set the models batch modifications.
*
* @param array $modifications
*
* @return $this
*/
public function setModifications(array $modifications = [])
{
$this->modifications = [];
foreach ($modifications as $modification) {
$this->addModification($modification);
}
return $this;
}
/**
* Adds a batch modification to the model.
*
* @param array|BatchModification $mod
*
* @throws InvalidArgumentException
*
* @return $this
*/
public function addModification($mod = [])
{
if ($mod instanceof BatchModification) {
$mod = $mod->get();
}
if ($this->isValidModification($mod)) {
$this->modifications[] = $mod;
return $this;
}
throw new InvalidArgumentException(
"The batch modification array does not include the mandatory 'attrib' or 'modtype' keys."
);
}
/**
* Get the model's guid attribute key name.
*
* @return string
*/
public function getGuidKey()
{
return $this->guidKey;
}
/**
* Get the model's ANR attributes for querying when incompatible with ANR.
*
* @return array
*/
public function getAnrAttributes()
{
return ['cn', 'sn', 'uid', 'name', 'mail', 'givenname', 'displayname'];
}
/**
* Get the name of the model, or the given DN.
*
* @param string|null $dn
*
* @return string|null
*/
public function getName($dn = null)
{
return $this->newDn($dn ?? $this->dn)->name();
}
/**
* Get the head attribute of the model, or the given DN.
*
* @param string|null $dn
*
* @return string|null
*/
public function getHead($dn = null)
{
return $this->newDn($dn ?? $this->dn)->head();
}
/**
* Get the RDN of the model, of the given DN.
*
* @param string|null
*
* @return string|null
*/
public function getRdn($dn = null)
{
return $this->newDn($dn ?? $this->dn)->relative();
}
/**
* Get the parent distinguished name of the model, or the given DN.
*
* @param string|null
*
* @return string|null
*/
public function getParentDn($dn = null)
{
return $this->newDn($dn ?? $this->dn)->parent();
}
/**
* Create a new Distinguished Name object.
*
* @param string|null $dn
*
* @return DistinguishedName
*/
public function newDn($dn = null)
{
return new DistinguishedName($dn);
}
/**
* Get the model's object GUID key.
*
* @return void
*/
public function getObjectGuidKey()
{
return $this->guidKey;
}
/**
* Get the model's binary object GUID.
*
* @see https://msdn.microsoft.com/en-us/library/ms679021(v=vs.85).aspx
*
* @return string|null
*/
public function getObjectGuid()
{
return $this->getFirstAttribute($this->guidKey);
}
/**
* Get the model's object classes.
*
* @return array
*/
public function getObjectClasses()
{
return $this->getAttribute('objectclass') ?: [];
}
/**
* Get the model's string GUID.
*
* @return string|null
*/
public function getConvertedGuid()
{
try {
return (string) new Guid($this->getObjectGuid());
} catch (InvalidArgumentException $e) {
return;
}
}
/**
* Determine if the current model is a direct descendant of the given.
*
* @param static|string $parent
*
* @return bool
*/
public function isChildOf($parent)
{
return $this->newDn($this->getDn())->isChildOf(
$this->newDn((string) $parent)
);
}
/**
* Determine if the current model is a direct ascendant of the given.
*
* @param static|string $child
*
* @return bool
*/
public function isParentOf($child)
{
return $this->newDn($this->getDn())->isParentOf(
$this->newDn((string) $child)
);
}
/**
* Determine if the current model is a descendant of the given.
*
* @param static|string $model
*
* @return bool
*/
public function isDescendantOf($model)
{
return $this->dnIsInside($this->getDn(), $model);
}
/**
* Determine if the current model is a ancestor of the given.
*
* @param static|string $model
*
* @return bool
*/
public function isAncestorOf($model)
{
return $this->dnIsInside($model, $this->getDn());
}
/**
* Determines if the DN is inside of the parent DN.
*
* @param static|string $dn
* @param static|string $parentDn
*
* @return bool
*/
protected function dnIsInside($dn, $parentDn)
{
return $this->newDn((string) $dn)->isDescendantOf(
$this->newDn($parentDn)
);
}
/**
* Set the base DN of where the model should be created in.
*
* @param static|string $dn
*
* @return $this
*/
public function inside($dn)
{
$this->in = $dn instanceof self ? $dn->getDn() : $dn;
return $this;
}
/**
* Save the model to the directory.
*
* @param array $attributes The attributes to update or create for the current entry.
*
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function save(array $attributes = [])
{
$this->fill($attributes);
$this->fireModelEvent(new Events\Saving($this));
$this->exists ? $this->performUpdate() : $this->performInsert();
$this->fireModelEvent(new Events\Saved($this));
$this->in = null;
}
/**
* Inserts the model into the directory.
*
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
protected function performInsert()
{
// Here we will populate the models object classes if it
// does not already have any set. An LDAP object cannot
// be successfully created in the server without them.
if (! $this->hasAttribute('objectclass')) {
$this->setAttribute('objectclass', static::$objectClasses);
}
$query = $this->newQuery();
// If the model does not currently have a distinguished
// name, we will attempt to generate one automatically
// using the current query builder's DN as the base.
if (empty($this->getDn())) {
$this->setDn($this->getCreatableDn());
}
$this->fireModelEvent(new Events\Creating($this));
// Here we perform the insert of new object in the directory,
// but filter out any empty attributes before sending them
// to the server. LDAP servers will throw an exception if
// attributes have been given empty or null values.
$query->insert($this->getDn(), array_filter($this->getAttributes()));
$this->fireModelEvent(new Events\Created($this));
$this->syncOriginal();
$this->exists = true;
$this->wasRecentlyCreated = true;
}
/**
* Updates the model in the directory.
*
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
protected function performUpdate()
{
if (! count($modifications = $this->getModifications())) {
return;
}
$this->fireModelEvent(new Events\Updating($this));
$this->newQuery()->update($this->dn, $modifications);
$this->fireModelEvent(new Events\Updated($this));
$this->syncOriginal();
$this->modifications = [];
}
/**
* Create the model in the directory.
*
* @param array $attributes The attributes for the new entry.
*
* @throws \LdapRecord\LdapRecordException
*
* @return Model
*/
public static function create(array $attributes = [])
{
$instance = new static($attributes);
$instance->save();
return $instance;
}
/**
* Create an attribute on the model.
*
* @param string $attribute The attribute to create
* @param mixed $value The value of the new attribute
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function createAttribute($attribute, $value)
{
$this->validateExistence();
$this->newQuery()->insertAttributes($this->dn, [$attribute => (array) $value]);
$this->addAttributeValue($attribute, $value);
}
/**
* Update the model.
*
* @param array $attributes The attributes to update for the current entry.
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function update(array $attributes = [])
{
$this->validateExistence();
$this->save($attributes);
}
/**
* Update the model attribute with the specified value.
*
* @param string $attribute The attribute to modify
* @param mixed $value The new value for the attribute
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function updateAttribute($attribute, $value)
{
$this->validateExistence();
$this->newQuery()->updateAttributes($this->dn, [$attribute => (array) $value]);
$this->addAttributeValue($attribute, $value);
}
/**
* Destroy the models for the given distinguished names.
*
* @param Collection|array|string $dns
* @param bool $recursive
*
* @throws \LdapRecord\LdapRecordException
*
* @return int
*/
public static function destroy($dns, $recursive = false)
{
$count = 0;
$dns = is_string($dns) ? (array) $dns : $dns;
$instance = new static();
foreach ($dns as $dn) {
if (! $model = $instance->find($dn)) {
continue;
}
$model->delete($recursive);
$count++;
}
return $count;
}
/**
* Delete the model from the directory.
*
* Throws a ModelNotFoundException if the current model does
* not exist or does not contain a distinguished name.
*
* @param bool $recursive Whether to recursively delete leaf nodes (models that are children).
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function delete($recursive = false)
{
$this->validateExistence();
$this->fireModelEvent(new Events\Deleting($this));
if ($recursive) {
$this->deleteLeafNodes();
}
$this->newQuery()->delete($this->dn);
// If the deletion is successful, we will mark the model
// as non-existing, and then fire the deleted event so
// developers can hook in and run further operations.
$this->exists = false;
$this->fireModelEvent(new Events\Deleted($this));
}
/**
* Deletes leaf nodes that are attached to the model.
*
* @throws \LdapRecord\LdapRecordException
*
* @return Collection
*/
protected function deleteLeafNodes()
{
return $this->newQueryWithoutScopes()
->in($this->dn)
->listing()
->paginate()
->each(function (self $model) {
$model->delete($recursive = true);
});
}
/**
* Delete an attribute on the model.
*
* @param string|array $attributes The attribute(s) to delete
*
* Delete specific values in attributes:
*
* ["memberuid" => "jdoe"]
*
* Delete an entire attribute:
*
* ["memberuid" => []]
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function deleteAttribute($attributes)
{
$this->validateExistence();
$attributes = $this->makeDeletableAttributes($attributes);
$this->newQuery()->deleteAttributes($this->dn, $attributes);
foreach ($attributes as $attribute => $value) {
// If the attribute value is empty, we can assume the
// attribute was completely deleted from the model.
// We will pull the attribute out and continue on.
if (empty($value)) {
unset($this->attributes[$attribute]);
}
// Otherwise, only specific attribute values have been
// removed. We will determine which ones have been
// removed and update the attributes value.
elseif (Arr::exists($this->attributes, $attribute)) {
$this->attributes[$attribute] = array_values(
array_diff($this->attributes[$attribute], (array) $value)
);
}
}
$this->syncOriginal();
}
/**
* Make a deletable attribute array.
*
* @param string|array $attributes
*
* @return array
*/
protected function makeDeletableAttributes($attributes)
{
$delete = [];
foreach (Arr::wrap($attributes) as $key => $value) {
is_int($key)
? $delete[$value] = []
: $delete[$key] = Arr::wrap($value);
}
return $delete;
}
/**
* Move the model into the given new parent.
*
* For example: $user->move($ou);
*
* @param static|string $newParentDn The new parent of the current model.
* @param bool $deleteOldRdn Whether to delete the old models relative distinguished name once renamed / moved.
*
* @throws UnexpectedValueException
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function move($newParentDn, $deleteOldRdn = true)
{
$this->validateExistence();
if (! $rdn = $this->getRdn()) {
throw new UnexpectedValueException('Current model does not contain an RDN to move.');
}
$this->rename($rdn, $newParentDn, $deleteOldRdn);
}
/**
* Rename the model to a new RDN and new parent.
*
* @param string $rdn The models new relative distinguished name. Example: "cn=JohnDoe"
* @param static|string|null $newParentDn The models new parent distinguished name (if moving). Leave this null if you are only renaming. Example: "ou=MovedUsers,dc=acme,dc=org"
* @param bool|true $deleteOldRdn Whether to delete the old models relative distinguished name once renamed / moved.
*
* @throws ModelDoesNotExistException
* @throws \LdapRecord\LdapRecordException
*
* @return void
*/
public function rename($rdn, $newParentDn = null, $deleteOldRdn = true)
{
$this->validateExistence();
if ($newParentDn instanceof self) {
$newParentDn = $newParentDn->getDn();
}
if (is_null($newParentDn)) {
$newParentDn = $this->getParentDn($this->dn);
}
// If the RDN and the new parent DN are the same as the current,
// we will simply return here to prevent a rename operation
// being sent, which would fail anyway in such case.
if (
$rdn === $this->getRdn()
&& $newParentDn === $this->getParentDn()
) {
return;
}
$this->fireModelEvent(new Renaming($this, $rdn, $newParentDn));
$this->newQuery()->rename($this->dn, $rdn, $newParentDn, $deleteOldRdn);
// If the model was successfully renamed, we will set
// its new DN so any further updates to the model
// can be performed without any issues.
$this->dn = implode(',', [$rdn, $newParentDn]);
$map = $this->newDn($this->dn)->assoc();
// Here we'll populate the models new primary
// RDN attribute on the model so we do not
// have to re-synchronize with the server.
$modelNameAttribute = key($map);
$this->attributes[$modelNameAttribute]
= $this->original[$modelNameAttribute]
= [reset($map[$modelNameAttribute])];
$this->fireModelEvent(new Renamed($this));
$this->wasRecentlyRenamed = true;
}
/**
* Get a distinguished name that is creatable for the model.
*
* @param string|null $name
* @param string|null $attribute
*
* @return string
*/
public function getCreatableDn($name = null, $attribute = null)
{
return implode(',', [
$this->getCreatableRdn($name, $attribute),
$this->in ?? $this->newQuery()->getbaseDn(),
]);
}
/**
* Get a creatable (escaped) RDN for the model.
*
* @param string|null $name
* @param string|null $attribute
*
* @return string
*/
public function getCreatableRdn($name = null, $attribute = null)
{
$attribute = $attribute ?? $this->getCreatableRdnAttribute();
$name = $this->escape(
$name ?? $this->getFirstAttribute($attribute)
)->dn();
return "$attribute=$name";
}
/**
* Get the creatable RDN attribute name.
*
* @return string
*/
protected function getCreatableRdnAttribute()
{
return 'cn';
}
/**
* Determines if the given modification is valid.
*
* @param mixed $mod
*
* @return bool
*/
protected function isValidModification($mod)
{
return Arr::accessible($mod)
&& Arr::exists($mod, BatchModification::KEY_MODTYPE)
&& Arr::exists($mod, BatchModification::KEY_ATTRIB);
}
/**
* Builds the models modifications from its dirty attributes.
*
* @return BatchModification[]
*/
protected function buildModificationsFromDirty()
{
$modifications = [];
foreach ($this->getDirty() as $attribute => $values) {
$modification = $this->newBatchModification($attribute, null, (array) $values);
if (Arr::exists($this->original, $attribute)) {
// If the attribute we're modifying has an original value, we will
// give the BatchModification object its values to automatically
// determine which type of LDAP operation we need to perform.
$modification->setOriginal($this->original[$attribute]);
}
if (! $modification->build()->isValid()) {
continue;
}
$modifications[] = $modification;
}
return $modifications;
}
/**
* Validates that the current model exists.
*
* @throws ModelDoesNotExistException
*
* @return void
*/
protected function validateExistence()
{
if (! $this->exists || is_null($this->dn)) {
throw ModelDoesNotExistException::forModel($this);
}
}
}