: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
$args = \func_get_args();
return \array_intersect_key( $this->data, \array_flip( $args ) );
* Returns the value of a property of this object (database row) or null if not present.
* If a column-names array is passed, it will return a associative array with the value of each column or null if
* @param string|array $key Key.
* @return array|mixed|null
public function get( $key ) {
if ( \is_array( $key ) ) {
foreach ( $key as $column ) {
$result[ $column ] = ( $this->data[ $column ] ?? null );
return ( $this->data[ $key ] ?? null );
* Returns the name of the column in the database table which contains the primary key ID of the row.
* @return string The primary key ID of the row.
protected function get_id_column_name() {
if ( ! \is_null( $this->instance_id_column ) ) {
return $this->instance_id_column;
* Gets the primary key ID of this object.
* @param bool $disallow_null Whether to allow null IDs.
* @return array|mixed|null
* @throws Exception Primary key ID contains null value(s).
* @throws Exception Primary key ID missing from row or is null.
public function id( $disallow_null = false ) {
$id = $this->get( $this->get_id_column_name() );
if ( \is_array( $id ) ) {
foreach ( $id as $id_part ) {
if ( $id_part === null ) {
throw new Exception( 'Primary key ID contains null value(s)' );
elseif ( $id === null ) {
throw new Exception( 'Primary key ID missing from row or is null' );
* Sets a property to a particular value on this object.
* To set multiple properties at once, pass an associative array as the first parameter and leave out the second
* parameter. Flags the properties as 'dirty' so they will be saved to the database when save() is called.
* @param string|array $key Key.
* @param string|null $value Value.
public function set( $key, $value = null ) {
return $this->set_orm_property( $key, $value );
* Set a property to a particular value on this object as expression.
* To set multiple properties at once, pass an associative array as the first parameter and leave out the second
* parameter. Flags the properties as 'dirty' so they will be saved to the database when save() is called.
* @param string|array $key Key.
* @param string|null $value Value.
public function set_expr( $key, $value = null ) {
return $this->set_orm_property( $key, $value, true );
* Sets a property on the ORM object.
* @param string|array $key Key.
* @param string|null $value Value.
* @param bool $expr Expression.
protected function set_orm_property( $key, $value = null, $expr = false ) {
if ( ! \is_array( $key ) ) {
$key = [ $key => $value ];
foreach ( $key as $field => $value ) {
$this->data[ $field ] = $value;
$this->dirty_fields[ $field ] = $value;
if ( $expr === false && isset( $this->expr_fields[ $field ] ) ) {
unset( $this->expr_fields[ $field ] );
elseif ( $expr === true ) {
$this->expr_fields[ $field ] = true;
* Checks whether the given field has been changed since this object was saved.
public function is_dirty( $key ) {
return \array_key_exists( $key, $this->dirty_fields );
* Checks whether the model was the result of a call to create() or not.
public function is_new() {
* Saves any fields which have been modified on this object to the database.
* @return bool True on success.
* @throws Exception Primary key ID contains null value(s).
* @throws Exception Primary key ID missing from row or is null.
// Remove any expression fields as they are already baked into the query.
$values = \array_values( \array_diff_key( $this->dirty_fields, $this->expr_fields ) );
// If there are no dirty values, do nothing.
if ( empty( $values ) && empty( $this->expr_fields ) ) {
$query = \implode( ' ', [ $this->build_update(), $this->add_id_column_conditions() ] );
if ( \is_array( $id ) ) {
$values = \array_merge( $values, \array_values( $id ) );
$query = $this->build_insert();
$success = self::execute( $query, $values );
// If we've just inserted a new record, set the ID of this object.
if ( $this->count_null_id_columns() !== 0 ) {
$column = $this->get_id_column_name();
// If the primary key is compound, assign the last inserted id to the first column.
if ( \is_array( $column ) ) {
$column = \reset( $column );
// Explicitly cast to int to make dealing with Id's simpler.
$this->data[ $column ] = (int) $wpdb->insert_id;
$this->dirty_fields = [];
* Extracts and gathers all dirty column names from the given model instances.
* @param array $models Array of model instances to be inserted.
* @return array The distinct set of columns that are dirty in at least one of the models.
* @throws InvalidArgumentException Instance to be inserted is not a new one.
public function get_dirty_column_names( $models ) {
$dirty_column_names = [];
foreach ( $models as $model ) {
if ( ! $model->orm->is_new() ) {
throw new InvalidArgumentException( 'Instance to be inserted is not a new one' );
// Remove any expression fields as they are already baked into the query.
$dirty_fields = \array_diff_key( $model->orm->dirty_fields, $model->orm->expr_fields );
$dirty_column_names = \array_merge( $dirty_column_names, $dirty_fields );
$dirty_column_names = \array_keys( $dirty_column_names );
return $dirty_column_names;
* Inserts multiple rows in a single query. Expects new rows as it's a strictly insert function, not an update one.
* @example From the Indexable_Link_Builder class: $this->seo_links_repository->query()->insert_many( $links );
* @param array $models Array of model instances to be inserted.
* @return bool True for successful insert, false for failed.
* @throws InvalidArgumentException Invalid instances to be inserted.
* @throws InvalidArgumentException Instance to be inserted is not a new one.
public function insert_many( $models ) {
// Validate the input first.
if ( ! \is_array( $models ) ) {
throw new InvalidArgumentException( 'Invalid instances to be inserted' );
if ( empty( $models ) ) {
* Filter: 'wpseo_chunk_bulked_insert_queries' - Allow filtering the chunk size of each bulked INSERT query.
* @param int $chunk_size The chunk size of the bulked INSERT queries.
$chunk = \apply_filters( 'wpseo_chunk_bulk_insert_queries', 100 );
$chunk = ! \is_int( $chunk ) ? 100 : $chunk;
$chunk = ( $chunk <= 0 ) ? 100 : $chunk;
$chunked_models = \array_chunk( $models, $chunk );
foreach ( $chunked_models as $models_chunk ) {
// First, we'll gather all the dirty fields throughout the models to be inserted.
$dirty_column_names = $this->get_dirty_column_names( $models_chunk );
// Now, we're creating all dirty fields throughout the models and
// setting them to null if they don't exist in each model.
foreach ( $models_chunk as $model ) {
foreach ( $dirty_column_names as $dirty_column ) {
// Set the value to null if it hasn't been set already.
if ( ! isset( $model->orm->dirty_fields[ $dirty_column ] ) ) {
$model->orm->dirty_fields[ $dirty_column ] = null;
// Only register the value if it is not null.
if ( ! \is_null( $model->orm->dirty_fields[ $dirty_column ] ) ) {
$model_values[] = $model->orm->dirty_fields[ $dirty_column ];
$values = \array_merge( $values, $model_values );
// We now have the same set of dirty columns in all our models and also gathered all values.
$query = $this->build_insert_many( $models_chunk, $dirty_column_names );
$success = $success && (bool) self::execute( $query, $values );
* Updates many records in the database.
* @return int|bool The number of rows changed if the query was succesful. False otherwise.
public function update_many() {
// Remove any expression fields as they are already baked into the query.
$values = \array_values( \array_diff_key( $this->dirty_fields, $this->expr_fields ) );
// If there are no dirty values, do nothing.
if ( empty( $values ) && empty( $this->expr_fields ) ) {
$query = $this->join_if_not_empty( ' ', [ $this->build_update(), $this->build_where() ] );
$success = self::execute( $query, \array_merge( $values, $this->values ) );
$this->dirty_fields = [];
* Adds a WHERE clause for every column that belongs to the primary key.
* @return string The where part of the query.
public function add_id_column_conditions() {
$keys = \is_array( $this->get_id_column_name() ) ? $this->get_id_column_name() : [ $this->get_id_column_name() ];
foreach ( $keys as $key ) {
$query[] = $this->quote_identifier( $key );
return \implode( ' ', $query );
* Builds an UPDATE query.
* @return string The update query.
protected function build_update() {
$query[] = "UPDATE {$this->quote_identifier($this->table_name)} SET";
foreach ( $this->dirty_fields as $key => $value ) {
if ( ! \array_key_exists( $key, $this->expr_fields ) ) {
$value = ( $value === null ) ? 'NULL' : '%s';
$field_list[] = "{$this->quote_identifier($key)} = {$value}";
$query[] = \implode( ', ', $field_list );
return \implode( ' ', $query );
* Builds an INSERT query.
* @return string The insert query.
protected function build_insert() {
$query[] = 'INSERT INTO';
$query[] = $this->quote_identifier( $this->table_name );
$field_list = \array_map( [ $this, 'quote_identifier' ], \array_keys( $this->dirty_fields ) );
$query[] = '(' . \implode( ', ', $field_list ) . ')';
$placeholders = $this->create_placeholders( $this->dirty_fields );
$query[] = "({$placeholders})";
return \implode( ' ', $query );
* Builds a bulk INSERT query.
* @param array $models Array of model instances to be inserted.
* @param array $dirty_column_names Array of dirty fields to be used in INSERT.
* @return string The insert query.
protected function build_insert_many( $models, $dirty_column_names ) {
$example_model = $models[0];
$total_placeholders = '';
$query[] = 'INSERT INTO';
$query[] = $this->quote_identifier( $example_model->orm->table_name );
$field_list = \array_map( [ $this, 'quote_identifier' ], $dirty_column_names );
$query[] = '(' . \implode( ', ', $field_list ) . ')';
// We assign placeholders per model for dirty fields that have values and NULL for dirty fields that don't.
foreach ( $models as $model ) {
foreach ( $dirty_column_names as $dirty_field ) {
$placeholder[] = ( $model->orm->dirty_fields[ $dirty_field ] === null ) ? 'NULL' : '%s';
$placeholders = \implode( ', ', $placeholder );
$total_placeholders .= "({$placeholders}),";
$query[] = \rtrim( $total_placeholders, ',' );
return \implode( ' ', $query );
* Deletes this record from the database.
* @return string The delete query.
* @throws Exception Primary key ID contains null value(s).
* @throws Exception Primary key ID missing from row or is null.
public function delete() {
$query = [ 'DELETE FROM', $this->quote_identifier( $this->table_name ), $this->add_id_column_conditions() ];
return self::execute( \implode( ' ', $query ), \is_array( $this->id( true ) ) ? \array_values( $this->id( true ) ) : [ $this->id( true ) ] );
* Deletes many records from the database.
* @return bool|int Response of wpdb::query.
public function delete_many() {
// Build and return the full DELETE statement by concatenating
// the results of calling each separate builder method.
$query = $this->join_if_not_empty(
$this->quote_identifier( $this->table_name ),
return self::execute( $query, $this->values );
* Checks whether the data has the key.
* @param mixed $offset Key.
* @return bool Whether the data has the key.
public function offsetExists( $offset ) {
return \array_key_exists( $offset, $this->data );
* Retrieves the value of the key.
* @param mixed $offset Key.
* @return array|mixed|null The value.
public function offsetGet( $offset ) {
return $this->get( $offset );
* Sets the value of the key.
* @param string|int $offset Key.
* @param mixed $value Value.
public function offsetSet( $offset, $value ) {
if ( \is_null( $offset ) ) {
$this->set( $offset, $value );
* Removes the given key from the data.
* @param mixed $offset Key.
public function offsetUnset( $offset ) {