赞
踩
教程:Hyperf
根据教程,可设置相关函数,如set属性名Attribute()、get属性名Attribute(),设置和获取属性。这在thinkphp中也常见。
修改器:set属性名Attribute();访问器:get属性名Attribute()。
模型的父类Hyperf\Database\Model\Model,定义__set()、_get()、__isset()、__unset()函数。
设置属性调用__set(),获取属性调用_get()。
__set()调用set属性名Attribute(),和格式化数据。先通过set属性名Attribute()获取值,再判断是否为日期格式化日期数据。若设置字段类型,会根据设定的字段类型匹配对应的类,返回对应类。会判断是否为json数据返回json格式字符换。若调用的对应字符串含有“->”,则将该对应类对象格式化为json字符串返回。
- #App\Controller\Test
- public function testmodifier() {
- $result = Article::query()->find(2)->toArray();
- var_dump($result);
- $article = Article::firstOrCreate(
- ['title' => 'test4'],
- ['user_id' => 2]
- );
- $result = Article::query()->where(['title' => '&test4'])->first()->toArray();
- var_dump($result);
- }
- #App1\Model\Article
- class Article extends Model implements CacheableInterface {
- use Cacheable;
- use SoftDeletes;
- /**
- * The table associated with the model.
- *
- * @var string
- */
- protected $table = 'articles';
- /**
- * The attributes that are mass assignable.
- *
- * @var array
- */
- protected $fillable = ['title', 'user_id']; //允许批量赋值
- /**
- * The attributes that should be cast to native types.
- *
- * @var array
- */
- protected $casts = ['id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime'];
-
- public function setTitleAttribute($value) {
- $this->attributes['title'] = "&" . $value;
- }
- public function getTitleAttribute($value) {
- return "标题:" . $value;
- }
- }
测试结果
- array(7) {
- ["id"]=>
- int(2)
- ["user_id"]=>
- int(1)
- ["title"]=>
- string(14) "标题:test2"
- ["created_at"]=>
- string(19) "2024-01-13 10:06:04"
- ["updated_at"]=>
- string(19) "2024-01-13 10:06:06"
- ["deleted_at"]=>
- NULL
- ["pv_num"]=>
- int(0)
- }
-
- array(7) {
- ["id"]=>
- int(10)
- ["user_id"]=>
- int(2)
- ["title"]=>
- string(15) "标题:&test4"
- ["created_at"]=>
- string(19) "2024-03-19 08:07:24"
- ["updated_at"]=>
- string(19) "2024-03-19 08:07:24"
- ["deleted_at"]=>
- NULL
- ["pv_num"]=>
- int(0)
- }
数据保存使用Hyperf\Database\Model\Builder::firstOrCreate()。firstOrNew()仅在对象中增加数据,未保存进数据库,这是和firstOrCreate()的区别。
过程中创建Hyperf\Database\Model\Model类对象是__construct(),会调用Model::fill()。Model::fill()使用Model::isFillable()调用Model::fillable属性,结果为true,才能设置属性,否则报错。
因为在Article::setTitleAttribute()对传入的属性增加数据。根据测试代码,查询的使用也应该加上“&”。
也是因为使用Builder::firstOrCreate()和Article::setTitleAttribute()修改传入属性,设置查询数据时不会查询到相应数据,因为查询值有差异。
tp中也遇到过相似情况。解决方法,对查询条件中数据也进行数据的换装,保证修改方式和保存之前的数据方式一样。
- #App1\Model\Article
-
- use Hyperf\DbConnection\Model\Model;
-
- class Article extends Model implements CacheableInterface {
- use Cacheable;
- use SoftDeletes;
- }
-
-
- #Hyperf\DbConnection\Model\Model
-
- use Hyperf\Database\Model\Model as BaseModel;
-
- class Model extends BaseModel
- {
- use HasContainer;
- use HasRepository;
- }
- #Hyperf\Database\Model\Model
-
- abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializable,
- CompressInterface {
- use Concerns\HasAttributes;
- use Concerns\HasEvents;
- use Concerns\HasGlobalScopes;
- use Concerns\HasRelationships;
- use Concerns\HasTimestamps;
- use Concerns\HidesAttributes;
- use Concerns\GuardsAttributes;
-
- /**
- * Dynamically retrieve attributes on the model.
- *
- * @param string $key
- */
- public function __get($key) {
- return $this->getAttribute($key);
- }
-
- /**
- * Dynamically set attributes on the model.
- *
- * @param string $key
- * @param mixed $value
- */
- public function __set($key, $value) {
- $this->setAttribute($key, $value);
- }
-
- /**
- * Determine if an attribute or relation 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
- */
- public function __unset($key) {
- $this->offsetUnset($key);
- }
-
- }
- # Hyperf\Database\Model\Concerns\HasAttributes
- /**
- * Set a given attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- */
- public function setAttribute($key, $value)
- {
- // First we will check for the presence of a mutator for the set operation
- // which simply lets the developers tweak the attribute as it is set on
- // the model, such as "json_encoding" an listing of data for storage.
- if ($this->hasSetMutator($key)) {
- return $this->setMutatedAttributeValue($key, $value);
- }
-
- // If an attribute is listed as a "date", we'll convert it from a DateTime
- // instance into a form proper for storage on the database tables using
- // the connection grammar's date format. We will auto set the values.
- if ($value && $this->isDateAttribute($key)) {
- $value = $this->fromDateTime($value);
- }
-
- if ($this->isClassCastable($key)) {
- $this->setClassCastableAttribute($key, $value);
-
- return $this;
- }
-
- if ($this->isJsonCastable($key) && !is_null($value)) {
- $value = $this->castAttributeAsJson($key, $value);
- }
-
- // If this attribute contains a JSON ->, we'll set the proper value in the
- // attribute's underlying array. This takes care of properly nesting an
- // attribute in the array's value in the case of deeply nested items.
- if (Str::contains($key, '->')) {
- return $this->fillJsonAttribute($key, $value);
- }
-
- $this->attributes[$key] = $value;
-
- return $this;
- }
- /**
- * Set the value of an attribute using its mutator.
- *
- * @param string $key
- * @param mixed $value
- */
- protected function setMutatedAttributeValue($key, $value)
- {
- return $this->{'set' . Str::studly($key) . 'Attribute'}($value);
- }
- /**
- * Convert a DateTime to a storable string.
- *
- * @param mixed $value
- * @return null|string
- */
- public function fromDateTime($value)
- {
- return empty($value) ? $value : $this->asDateTime($value)->format(
- $this->getDateFormat()
- );
- }
- /**
- * Get the format for database stored dates.
- *
- * @return string
- */
- public function getDateFormat()
- {
- return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
- }
- /**
- * Set the value of a class castable attribute.
- *
- * @param string $key
- * @param mixed $value
- */
- protected function setClassCastableAttribute($key, $value)
- {
- $caster = $this->resolveCasterClass($key);
-
- if (is_null($value)) {
- $this->attributes = array_merge($this->attributes, array_map(
- function () {
- },
- $this->normalizeCastClassResponse($key, $caster->set(
- $this,
- $key,
- $this->{$key},
- $this->attributes
- ))
- ));
- } else {
- $this->attributes = array_merge(
- $this->attributes,
- $this->normalizeCastClassResponse($key, $caster->set(
- $this,
- $key,
- $value,
- $this->attributes
- ))
- );
- }
-
- if ($caster instanceof CastsInboundAttributes || !is_object($value)) {
- unset($this->classCastCache[$key]);
- } else {
- $this->classCastCache[$key] = $value;
- }
- }
- /**
- * Cast the given attribute to JSON.
- *
- * @param string $key
- * @param mixed $value
- * @return string
- */
- protected function castAttributeAsJson($key, $value)
- {
- $value = $this->asJson($value);
-
- if ($value === false) {
- throw JsonEncodingException::forAttribute(
- $this,
- $key,
- json_last_error_msg()
- );
- }
-
- return $value;
- }
- /**
- * Set a given JSON attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- * @return $this
- */
- public function fillJsonAttribute($key, $value)
- {
- [$key, $path] = explode('->', $key, 2);
-
- $this->attributes[$key] = $this->asJson($this->getArrayAttributeWithValue(
- $path,
- $key,
- $value
- ));
-
- return $this;
- }
模型会将 created_at
和 updated_at
字段转换为 Carbon\Carbon 实例,它继承了 PHP
原生的 DateTime
类并提供了各种有用的方法。可以通过设置模型的 $dates
属性来添加其他日期属性。
调用Model::_get()、Model::_set()时,会判断字段类型,为日期则转换为Carbon\Carbon类对象。可以设置日期格式。
$date为日期类型字段,$dateFormat为日期格式字符串,都在Hyperf\Database\Model\Concerns\HasAttributes中设置,也是由其转换数据类型。
HasAttributes::castAttribute()处理各种字段类型,HasAttributes::asDate()执行日期类型转换,HasAttributes::getDateFormat()获取日期格式。
日期类型默认包括created_at
、updated_at。日期
默认格式
"Y-m-d H:i:s"。
2.2 测试
- #App1\Model\Article
- protected $dateFormat = 'Y-m-d H:i';
- public function setTitleAttribute($value) {
- $this->attributes['title'] = $value;
- }
- public function getTitleAttribute($value) {
- return $value;
- }
- #App\Controller\TestController
- public function testmodifier() {
- $article = Article::firstOrCreate(
- ['title' => 'test4'],
- ['user_id' => 2]
- );
- var_dump($article->toArray());
- }
测试结果
- array(7) {
- ["id"]=>
- int(11)
- ["user_id"]=>
- int(2)
- ["title"]=>
- string(5) "test4"
- ["created_at"]=>
- string(16) "2024-03-22 09:04"
- ["updated_at"]=>
- string(16) "2024-03-22 09:04"
- ["deleted_at"]=>
- NULL
- ["pv_num"]=>
- int(0)
- }
测试可见 数据库中时间格式还是h:i:s,仅获取的时候是h:i格式。
Model::CREATED_AT、Model::UPDATED_AT使用Carbon::now()获取时间,并没有使用$dateFormat属性。
2.3 源码
- #Hyperf\Database\Model\Model
- public function __get($key) {
- return $this->getAttribute($key);
- }
- public function __set($key, $value) {
- $this->setAttribute($key, $value);
- }
- /**
- * 新增时使用
- *
- * @param \Hyperf\Database\Model\Builder $query
- * @return bool
- */
- protected function performInsert(Builder $query) {
- if ($event = $this->fireModelEvent('creating')) {
- if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
- return false;
- }
- }
-
- // First we'll need to create a fresh query instance and touch the creation and
- // update timestamps on this model, which are maintained by us for developer
- // convenience. After, we will just continue saving these model instances.
- if ($this->usesTimestamps()) {
- $this->updateTimestamps();
- }
-
- // If the model has an incrementing key, we can use the "insertGetId" method on
- // the query builder, which will give us back the final inserted ID for this
- // table from the database. Not all tables have to be incrementing though.
- $attributes = $this->getAttributes();
-
- if ($this->getIncrementing()) {
- $this->insertAndSetId($query, $attributes);
- }
-
- // If the table isn't incrementing we'll simply insert these attributes as they
- // are. These attribute arrays must contain an "id" column previously placed
- // there by the developer as the manually determined key for these models.
- else {
- if (empty($attributes)) {
- return true;
- }
-
- $query->insert($attributes);
- }
-
- // We will go ahead and set the exists property to true, so that it is set when
- // the created event is fired, just in case the developer tries to update it
- // during the event. This will allow them to do so and run an update here.
- $this->exists = true;
-
- $this->wasRecentlyCreated = true;
-
- $this->fireModelEvent('created');
-
- return true;
- }
- /**
- * 修改时使用
- *
- * @param \Hyperf\Database\Model\Builder $query
- * @return bool
- */
- protected function performUpdate(Builder $query) {
- // If the updating event returns false, we will cancel the update operation so
- // developers can hook Validation systems into their models and cancel this
- // operation if the model does not pass validation. Otherwise, we update.
- if ($event = $this->fireModelEvent('updating')) {
- if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
- return false;
- }
- }
-
- // First we need to create a fresh query instance and touch the creation and
- // update timestamp on the model which are maintained by us for developer
- // convenience. Then we will just continue saving the model instances.
- if ($this->usesTimestamps()) {
- $this->updateTimestamps();
- }
-
- // Once we have run the update operation, we will fire the "updated" event for
- // this model instance. This will allow developers to hook into these after
- // models are updated, giving them a chance to do any special processing.
- $dirty = $this->getDirty();
-
- if (count($dirty) > 0) {
- $this->setKeysForSaveQuery($query)->update($dirty);
-
- $this->syncChanges();
-
- $this->fireModelEvent('updated');
- }
-
- return true;
- }
- public function save(array $options = []): bool {
- $this->mergeAttributesFromClassCasts();
-
- $query = $this->newModelQuery();
-
- // If the "saving" event returns false we'll bail out of the save and return
- // false, indicating that the save failed. This provides a chance for any
- // listeners to cancel save operations if validations fail or whatever.
- if ($saving = $this->fireModelEvent('saving')) {
- if ($saving instanceof StoppableEventInterface && $saving->isPropagationStopped()) {
- return false;
- }
- }
-
- // If the model already exists in the database we can just update our record
- // that is already in this database using the current IDs in this "where"
- // clause to only update this model. Otherwise, we'll just insert them.
- if ($this->exists) {
- $saved = $this->isDirty() ? $this->performUpdate($query) : true;
- } else {
- // If the model is brand new, we'll insert it into our database and set the
- // ID attribute on the model to the value of the newly inserted row's ID
- // which is typically an auto-increment value managed by the database.
- $saved = $this->performInsert($query);
-
- if (!$this->getConnectionName() && $connection = $query->getConnection()) {
- $this->setConnection($connection->getName());
- }
- }
-
- // If the model is successfully saved, we need to do a few more things once
- // that is done. We will call the "saved" method here to run any actions
- // we need to happen after a model gets successfully saved right here.
- if ($saved) {
- $this->finishSave($options);
- }
-
- return $saved;
- }
- #Hyperf\Database\Model\Concerns\HasAttributes
- /**
- * Set a given attribute on the model.
- *
- * @param string $key
- * @param mixed $value
- */
- public function setAttribute($key, $value)
- {
- // First we will check for the presence of a mutator for the set operation
- // which simply lets the developers tweak the attribute as it is set on
- // the model, such as "json_encoding" an listing of data for storage.
- if ($this->hasSetMutator($key)) {
- return $this->setMutatedAttributeValue($key, $value);
- }
-
- // If an attribute is listed as a "date", we'll convert it from a DateTime
- // instance into a form proper for storage on the database tables using
- // the connection grammar's date format. We will auto set the values.
- if ($value && $this->isDateAttribute($key)) {
- $value = $this->fromDateTime($value);
- }
-
- if ($this->isClassCastable($key)) {
- $this->setClassCastableAttribute($key, $value);
-
- return $this;
- }
-
- if ($this->isJsonCastable($key) && !is_null($value)) {
- $value = $this->castAttributeAsJson($key, $value);
- }
-
- // If this attribute contains a JSON ->, we'll set the proper value in the
- // attribute's underlying array. This takes care of properly nesting an
- // attribute in the array's value in the case of deeply nested items.
- if (Str::contains($key, '->')) {
- return $this->fillJsonAttribute($key, $value);
- }
-
- $this->attributes[$key] = $value;
-
- return $this;
- }
- public function fromDateTime($value)
- {
- return empty($value) ? $value : $this->asDateTime($value)->format(
- $this->getDateFormat()
- );
- }
- /**
- * Get an attribute from the model.
- *
- * @param string $key
- */
- public function getAttribute($key)
- {
- if (!$key) {
- return;
- }
-
- // If the attribute exists in the attribute array or has a "get" mutator we will
- // get the attribute's value. Otherwise, we will proceed as if the developers
- // are asking for a relationship's value. This covers both types of values.
- if (array_key_exists($key, $this->getAttributes())
- || $this->hasGetMutator($key)
- || $this->isClassCastable($key)) {
- return $this->getAttributeValue($key);
- }
- // Here we will determine if the model base class itself contains this given key
- // since we don't want to treat any of those methods as relationships because
- // they are all intended as helper methods and none of these are relations.
- if (method_exists(self::class, $key)) {
- return;
- }
- return $this->getRelationValue($key);
- }
- public function getAttributeValue($key)
- {
- return $this->transformModelValue($key, $this->getAttributeFromArray($key));
- }
- protected function transformModelValue($key, $value)
- {
- // If the attribute has a get mutator, we will call that then return what
- // it returns as the value, which is useful for transforming values on
- // retrieval from the model to a form that is more useful for usage.
- if ($this->hasGetMutator($key)) {
- return $this->mutateAttribute($key, $value);
- }
-
- // If the attribute exists within the cast array, we will convert it to
- // an appropriate native PHP type dependent upon the associated value
- // given with the key in the pair. Dayle made this comment line up.
- if ($this->hasCast($key)) {
- return $this->castAttribute($key, $value);
- }
-
- // If the attribute is listed as a date, we will convert it to a DateTime
- // instance on retrieval, which makes it quite convenient to work with
- // date fields without having to create a mutator for each property.
- if ($value !== null
- && \in_array($key, $this->getDates(), false)) {
- return $this->asDateTime($value);
- }
-
- return $value;
- }
- protected function castAttribute($key, $value)
- {
- $castType = $this->getCastType($key);
-
- if (is_null($value) && in_array($castType, static::$primitiveCastTypes)) {
- return $value;
- }
-
- switch ($castType) {
- case 'int':
- case 'integer':
- return (int) $value;
- case 'real':
- case 'float':
- case 'double':
- return $this->fromFloat($value);
- case 'decimal':
- return $this->asDecimal($value, explode(':', $this->getCasts()[$key], 2)[1]);
- case 'string':
- return (string) $value;
- case 'bool':
- case 'boolean':
- return (bool) $value;
- case 'object':
- return $this->fromJson($value, true);
- case 'array':
- case 'json':
- return $this->fromJson($value);
- case 'collection':
- return new BaseCollection($this->fromJson($value));
- case 'date':
- return $this->asDate($value);
- case 'datetime':
- case 'custom_datetime':
- return $this->asDateTime($value);
- case 'timestamp':
- return $this->asTimestamp($value);
- }
-
- if ($this->isClassCastable($key)) {
- return $this->getClassCastableAttributeValue($key, $value);
- }
-
- return $value;
- }
- protected function asDate($value)
- {
- return $this->asDateTime($value)->startOfDay();
- }
- protected function asDateTime($value)
- {
- // If this value is already a Carbon instance, we shall just return it as is.
- // This prevents us having to re-instantiate a Carbon instance when we know
- // it already is one, which wouldn't be fulfilled by the DateTime check.
- if ($value instanceof Carbon || $value instanceof CarbonInterface) {
- return Carbon::instance($value);
- }
-
- // If the value is already a DateTime instance, we will just skip the rest of
- // these checks since they will be a waste of time, and hinder performance
- // when checking the field. We will just return the DateTime right away.
- if ($value instanceof DateTimeInterface) {
- return Carbon::parse(
- $value->format('Y-m-d H:i:s.u'),
- $value->getTimezone()
- );
- }
-
- // If this value is an integer, we will assume it is a UNIX timestamp's value
- // and format a Carbon object from this timestamp. This allows flexibility
- // when defining your date fields as they might be UNIX timestamps here.
- if (is_numeric($value)) {
- return Carbon::createFromTimestamp($value);
- }
-
- // If the value is in simply year, month, day format, we will instantiate the
- // Carbon instances from that format. Again, this provides for simple date
- // fields on the database, while still supporting Carbonized conversion.
- if ($this->isStandardDateFormat($value)) {
- return Carbon::instance(Carbon::createFromFormat('Y-m-d', $value)->startOfDay());
- }
-
- $format = $this->getDateFormat();
-
- // Finally, we will just assume this date is in the format used by default on
- // the database connection and use that format to create the Carbon object
- // that is returned back out to the developers after we convert it here.
- if (Carbon::hasFormat($value, $format)) {
- return Carbon::createFromFormat($format, $value);
- }
-
- return Carbon::parse($value);
- }
- public function getDateFormat()
- {
- return $this->dateFormat ?: $this->getConnection()->getQueryGrammar()->getDateFormat();
- }
- #Hyperf\Database\Grammar
- public function getDateFormat()
- {
- return 'Y-m-d H:i:s';
- }
- #Hyperf\Database\Model\Concerns\HasTimestamps
- protected function updateTimestamps()
- {
- $time = $this->freshTimestamp();
-
- if (! is_null(static::UPDATED_AT) && ! $this->isDirty(static::UPDATED_AT)) {
- $this->setUpdatedAt($time);
- }
-
- if (! $this->exists && ! is_null(static::CREATED_AT)
- && ! $this->isDirty(static::CREATED_AT)) {
- $this->setCreatedAt($time);
- }
- }
- public function setCreatedAt($value)
- {
- $this->{static::CREATED_AT} = $value;
-
- return $this;
- }
-
- public function setUpdatedAt($value)
- {
- $this->{static::UPDATED_AT} = $value;
-
- return $this;
- }
- public function freshTimestamp()
- {
- return Carbon::now();
- }
- #Carbon\Traits\Creator
- public function __construct($time = null, $tz = null)
- {
- if ($time instanceof DateTimeInterface) {
- $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u');
- }
-
- if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) {
- $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP');
- }
-
- // If the class has a test now set and we are trying to create a now()
- // instance then override as required
- $isNow = empty($time) || $time === 'now';
-
- if (method_exists(static::class, 'hasTestNow') &&
- method_exists(static::class, 'getTestNow') &&
- static::hasTestNow() &&
- ($isNow || static::hasRelativeKeywords($time))
- ) {
- static::mockConstructorParameters($time, $tz);
- }
-
- // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127
- if (!str_contains((string) .1, '.')) {
- $locale = setlocale(LC_NUMERIC, '0'); // @codeCoverageIgnore
- setlocale(LC_NUMERIC, 'C'); // @codeCoverageIgnore
- }
-
- try {
- parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null);
- } catch (Exception $exception) {
- throw new InvalidFormatException($exception->getMessage(), 0, $exception);
- }
-
- $this->constructedObjectId = spl_object_hash($this);
-
- if (isset($locale)) {
- setlocale(LC_NUMERIC, $locale); // @codeCoverageIgnore
- }
-
- self::setLastErrors(parent::getLastErrors());
- }
- public static function now($tz = null)
- {
- return new static(null, $tz);
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。