stellarwp/models

一个简单模型结构的库。

1.2.1 2023-09-12 15:59 UTC

This package is auto-updated.

Last update: 2024-09-09 15:26:52 UTC


README

一个简单模型结构的库。

目录

安装

建议通过 Composer 将 Schema 作为项目依赖项安装

composer require stellarwp/models

我们实际上建议使用 Strauss 将此库包含在您的项目中。

幸运的是,将 Strauss 添加到您的 composer.json 的复杂性仅略高于添加典型依赖项,所以请查看我们的 strauss 文档

示例说明

由于建议使用 Strauss 为此库的命名空间添加前缀,所有示例都将使用 Boomshakalaka 命名空间前缀。

配置

在此库的类可以使用之前,需要对其进行一些配置。配置通过 Config 类完成。

use Boomshakalaka\StellarWP\Models\Config;

add_action( 'plugins_loaded', function() {
	Config::setHookPrefix( 'boom-shakalaka' );
} );

创建模型

模型是包含数据并提供与该数据交互的一些辅助方法的类。

简单的模型

这是一个仅持有属性的模型示例。

namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Model;

class Breakfast_Model extends Model {
	/**
	 * @inheritDoc
	 */
	protected $properties = [
		'id'        => 'int',
		'name'      => 'string',
		'price'     => 'float',
		'num_eggs'  => 'int',
		'has_bacon' => 'bool',
	];
}

只读模型

这是一个仅读取和存储数据的模型。在大多数情况下,读取操作应委托给仓库类,但模型应提供一个简单的接口来与仓库交互。您可以通过实现 Contracts\ModelReadOnly 契约创建只读模型。

namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Contracts;
use Boomshakalaka\StellarWP\Models\Model;
use Boomshakalaka\StellarWP\Models\ModelQueryBuilder;

class Breakfast_Model extends Model implements Contracts\ModelReadOnly {
	/**
	 * @inheritDoc
	 */
	protected $properties = [
		'id'        => 'int',
		'name'      => 'string',
		'price'     => 'float',
		'num_eggs'  => 'int',
		'has_bacon' => 'bool',
	];

	/**
	 * @inheritDoc
	 */
	public static function find( $id ) : Model {
		return App::get( Repository::class )->get_by_id( $id );
	}

	/**
	 * @inheritDoc
	 */
	public static function query() : ModelQueryBuilder {
		return App::get( Repository::class )->prepare_query();
	}
}

CRUD 模型

这是一个包含 CRUD 操作的模型。理想情况下,实际的 CRUD 操作应委托给并由仓库类处理,但模型应提供一个简单的接口来与仓库交互。我们通过实现 Contracts\ModelCrud 契约获得 CRUD 模型。

namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Contracts;
use Boomshakalaka\StellarWP\Models\Model;
use Boomshakalaka\StellarWP\Models\ModelQueryBuilder;

class Breakfast_Model extends Model implements Contracts\ModelCrud {
	/**
	 * @inheritDoc
	 */
	protected $properties = [
		'id'        => 'int',
		'name'      => 'string',
		'price'     => 'float',
		'num_eggs'  => 'int',
		'has_bacon' => 'bool',
	];

	/**
	 * @inheritDoc
	 */
	public static function create( array $attributes ) : Model {
		$obj = new static( $attributes );

		return App::get( Repository::class )->insert( $obj );
	}

	/**
	 * @inheritDoc
	 */
	public static function find( $id ) : Model {
		return App::get( Repository::class )->get_by_id( $id );
	}

	/**
	 * @inheritDoc
	 */
	public function save() : Model {
		return App::get( Repository::class )->update( $this );
	}

	/**
	 * @inheritDoc
	 */
	public function delete() : bool {
		return App::get( Repository::class )->delete( $this );
	}

	/**
	 * @inheritDoc
	 */
	public static function query() : ModelQueryBuilder {
		return App::get( Repository::class )->prepareQuery();
	}
}

属性验证

有时验证在模型中设置的属性可能会有所帮助。为此,您可以创建 validate_*() 方法,该方法将在设置任何属性时执行。

以下是一个示例

namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Model;

class Breakfast_Model extends Model {
	/**
	 * @inheritDoc
	 */
	protected $properties = [
		'id'        => 'int',
		'name'      => 'string',
		'price'     => 'float',
		'num_eggs'  => 'int',
		'has_bacon' => 'bool',
	];

	/**
	 * Validate the name.
	 *
	 * @param string $value
	 *
	 * @return bool
	 */
	public function validate_name( $value ): bool {
		if ( ! preg_match( '/eggs/i', $value ) ) {
			throw new \Exception( 'Breakfasts must have "eggs" in the name!' );
		}

		return true;
	}
}

数据传输对象

数据传输对象(DTO)是帮助将数据库查询结果(或其他数据源)转换为模型的类。DTO 不是使用此库的必需品,但它们是推荐的。使用这些对象可以帮助您更谨慎地使用查询,并允许模型和仓库与 ModelQueryBuilder 良好地协作。

以下是一个早餐 DTO 的示例

namespace Boomshakalaka\Whatever;

use Boomshakalaka\Whatever\StellarWP\Models\DataTransferObject;
use Boomshakalaka\Whatever\Breakfast_Model;

class Breakfast_DTO extends DataTransferObject {
	/**
	 * Breakfast ID.
	 *
	 * @var int
	 */
	 public int $id;

	/**
	 * Breakfast name.
	 *
	 * @var string
	 */
	 public string $name;

	/**
	 * Breakfast price.
	 *
	 * @var float
	 */
	 public float $price;

	/**
	 * Number of eggs in the breakfast.
	 *
	 * @var int
	 */
	 public int $num_eggs;

	/**
	 * Whether or not the breakfast has bacon.
	 *
	 * @var bool
	 */
	 public bool $has_bacon;

	/**
	 * Builds a new DTO from an object.
	 *
	 * @since TBD
	 *
	 * @param object $object The object to build the DTO from.
	 *
	 * @return Breakfast_DTO The DTO instance.
	 */
	public static function fromObject( $object ): self {
		$self = new self();

		$self->id        = $object->id;
		$self->name      = $object->name;
		$self->price     = $object->price;
		$self->num_eggs  = $object->num_eggs;
		$self->has_bacon = (bool) $object->has_bacon;

		return $self;
	}

	/**
	 * Builds a model instance from the DTO.
	 *
	 * @since TBD
	 *
	 * @return Breakfast_Model The model instance.
	 */
	public function toModel(): Breakfast_Model {
		$attributes = get_object_vars( $this );

		return new Breakfast_Model( $attributes );
	}
}

仓库

仓库是从数据库中检索并与之交互的类。理想情况下,仓库应用于以不同方式查询数据库并返回相应的模型。使用此库时,我们提供了 DeletableInsertableUpdatable 契约,可用于指示仓库提供的操作。

你可能想知道为什么没有FindableReadable合约(或类似)。这是因为仓库的获取需求会根据用例而变化。然而,在Repository抽象类中,有一个抽象的prepareQuery()方法。这个方法应该返回一个可以用来从数据库中获取数据的ModelQueryBuilder实例。

namespace Boomshakalaka\Whatever;

use Boomshakalaka\StellarWP\Models\Contracts\Model;
use Boomshakalaka\StellarWP\Models\ModelQueryBuilder;
use Boomshakalaka\StellarWP\Repositories\Repository;
use Boomshakalaka\StellarWP\Repositories\Contracts;
use Boomshakalaka\Whatever\Breakfast_Model;
use Boomshakalaka\Whatever\Breakfast as Table;

class Breakfast_Repository extends Repository implements Contracts\Deletable, Contracts\Insertable, Contracts\Updatable {
	/**
	 * {@inheritDoc}
	 */
	public function delete( Model $model ): bool {
		return (bool) DB::delete( Table::table_name(), [ 'id' => $model->id ], [ '%d' ] );
	}

	/**
	 * {@inheritDoc}
	 */
	public function insert( Model $model ): Breakfast_Model {
		DB::insert( Table::table_name(), [
			'name' => $model->name,
			'price' => $model->price,
			'num_eggs' => $model->num_eggs,
			'has_bacon' => (int) $model->has_bacon,
		], [
			'%s',
			'%s',
			'%d',
			'%d',
		] );

		$model->id = DB::last_insert_id();

		return $model;
	}

	/**
	 * {@inheritDoc}
	 */
	function prepareQuery(): ModelQueryBuilder {
		$builder = new ModelQueryBuilder( Breakfast_Model::class );

		return $builder->from( Table::table_name( false ) );
	}

	/**
	 * {@inheritDoc}
	 */
	public function update( Model $model ): Model {
		DB::update( Table::table_name(), [
			'name' => $model->name,
			'price' => $model->price,
			'num_eggs' => $model->num_eggs,
			'has_bacon' => (int) $model->has_bacon,
		], [ 'id' => $model->id ], [
			'%s',
			'%s',
			'%d',
			'%d',
		], [ '%d' ] );

		return $model;
	}

	/**
	 * Finds a Breakfast by its ID.
	 *
	 * @since TBD
	 *
	 * @param int $id The ID of the Breakfast to find.
	 *
	 * @return Breakfast_Model|null The Breakfast model instance, or null if not found.
	 */
	public function find_by_id( int $id ): ?Breakfast_Model {
		return $this->prepareQuery()->where( 'id', $id )->get();
	}
}

与仓库交互

查询

$breakfast = App::get( Breakfast_Repository::class )->find_by_id( 1 );

// Or, we can fetch via the model, which defers to the repository.
$breakfast = Breakfast_Model::find( 1 );

插入

$breakfast = new Breakfast_Model( [
	'name'      => 'Bacon and Eggs',
	'price'     => 5.99,
	'num_eggs'  => 2,
	'has_bacon' => true,
] );

$breakfast->save();

更新

$breakfast = Breakfast_Model::find( 1 );
$breakfast->setAttribute( 'price', 6.99 );
$breakfast->save();

删除

$breakfast = Breakfast_Model::find( 1 );
$breakfast->delete();

值得注意的类

Model

这是一个抽象类,用于扩展你的模型。

ModelFactory

这是一个抽象类,用于扩展以创建模型工厂。

ModelQueryBuilder

该类扩展了stellarwp/dbQueryBuilder类,使其返回模型实例而不是数组或stdClass实例。使用此功能需要实现ModelFromQueryBuilderObject接口的模型。

DataTransferObject

这是一个用于扩展你的DTOs的抽象类。

Repositories\Repository

这是一个用于扩展你的仓库的抽象类。

值得注意的契约

Contracts\ModelCrud

在模型中提供CRUD操作的方法定义。

Contracts\ModelHasFactory

在模型中提供工厂方法的定义。

Contracts\ModelReadOnly

在模型中提供读取操作的方法签名。

Repositories\Contracts\Deletable

在仓库中提供删除方法的方法签名。

Repositories\Contracts\Insertable

在仓库中提供插入方法的方法签名。

Repositories\Contracts\Updatable

在仓库中提供更新方法的方法签名。