hyperf 二十九 修改器 二

教程:Hyperf

属性类型转换

Hyperf\Database\Model\Concerns\HasAttributes::casts被HasAttributes::setAttribute()、HasAttributes::getAttribute()调用,执行类型转换。

HasAttributes::casts为数组类型可设置基本类型或类的实例。默认设置主键id为int。

内置的基本强制转换类型:

  • json:'array', 'json', 'object', 'collection'
  • 日期:'date', 'datetime'
  • 整数:'int'\integer
  • 浮点数: real\float\double
  • 数字:decimal
  • 字符串:string
  • 布尔:bool/boolean
  • 自定义时间:custom_datetime
  • 时间戳:timestamp

decimal 可设置小数位数,格式decimal:小数位数,通过number_format()实现。

一 自定义类型转换

通过继承接口(implements)Hyperf\Contract\CastsAttributes,定义好后使用其类名称将其附加到HasAttributes::casts。

1.1 值对象类型转换

将值转换成对象。通过get()获取时设置为对象、set()设置时将值设置到对象中。

执行save()之前需要设置相应的对象。官网例子中是先获取在设置,这样获取之后数据结构中自带类。

更改数据结构中类对象值后,因为缓存原因,其类的值不会通过HasAttributes::getAttributes()获得刷新,使用HasAttributes::syncAttributes()通过缓存和属性值合并可获取设置后的更新数据。

1.2 入站类型转换

设置set()实现入站类型转换。

1.3 类型参数转换

使用":"设置参数。

php 复制代码
protected $casts = [
        'secret' => Hash::class.':sha256',
    ];

1.4 测试

获取

php 复制代码
#App\Controlle\TestController
public function testmodifier() {
        $r = Article::query()->find(2);
        $article = $r->article;
        $istop = $r->is_top;
        var_dump($istop);
        $r->article->intro = "test1";
        $info1 = $r->getAttributes();
        $info2 = $r->syncAttributes()->getAttributes();
        $r->intro = "test2";
        $info3 = $r->getAttributes();
        var_dump($article, $info1, $info2, $info3);
}
php 复制代码
#App1\Model\Article
protected $casts = [
        'id' => 'integer',
        'created_at' => 'datetime',
        'updated_at' => 'datetime',
        'is_top' => 'boolean',
        'article' => ArticleCasts::class,
    ];
php 复制代码
#App1\Attributes\Article 
namespace App1\Attributes;

class Article {

    public $title;
    public $content;
    public $intro;

    public function __construct($title, $intro, $content) {
        $this->title = $title;
        $this->content = $content;
        $this->intro = $intro;
    }
}
php 复制代码
#App1\Casts\ArticleCasts 
namespace App1\Casts;

use App1\Attributes\Article;
use Hyperf\Contract\CastsAttributes;

class ArticleCasts implements CastsAttributes {
    /**
     * 将取出的数据进行转换
     */
    public function get($model, $key, $value, $attributes): Article {
        return new Article(
            $attributes['title'],
            $attributes['intro'],
            $attributes['content']
        );
    }

    /**
     * 转换成将要进行存储的值
     */
    public function set($model, $key, $value, $attributes) {
        return [
            'title' => $value->title,
            'intro' => $value->intro,
            'content' => $value->content,
        ];
    }
}

测试结果

php 复制代码
bool(false)
object(App1\Attributes\Article)#1213 (3) {
  ["title"]=>
  string(5) "test2"
  ["content"]=>
  NULL
  ["intro"]=>
  string(5) "test1"
}
array(10) {
  ["id"]=>
  int(2)
  ["user_id"]=>
  int(1)
  ["title"]=>
  string(5) "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)
  ["intro"]=>
  NULL
  ["content"]=>
  NULL
  ["is_top"]=>
  int(0)
}
array(10) {
  ["id"]=>
  int(2)
  ["user_id"]=>
  int(1)
  ["title"]=>
  string(5) "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)
  ["intro"]=>
  string(5) "test1"
  ["content"]=>
  NULL
  ["is_top"]=>
  int(0)
}
array(10) {
  ["id"]=>
  int(2)
  ["user_id"]=>
  int(1)
  ["title"]=>
  string(5) "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)
  ["intro"]=>
  string(5) "test2"
  ["content"]=>
  NULL
  ["is_top"]=>
  int(0)
}

写入

php 复制代码
#App\Controlle\TestController
public function testmodifier() {
        $r = Article::query()->find(2);
        $r->is_top = 1;
        $r->article->intro = "test2";
        $attr = $r->getAttributes();
        var_dump($attr);
        $r->save();
        $attr = $r->getAttributes();
        var_dump($attr);
}

测试结果

php 复制代码
array(10) {
  ["id"]=>
  int(2)
  ["user_id"]=>
  int(1)
  ["title"]=>
  string(5) "test2"
  ["created_at"]=>
  string(19) "2024-01-13 10:06:04"
  ["updated_at"]=>
  string(19) "2024-03-25 09:47:00"
  ["deleted_at"]=>
  NULL
  ["pv_num"]=>
  int(0)
  ["intro"]=>
  string(5) "test1"
  ["content"]=>
  NULL
  ["is_top"]=>
  int(1)
}
array(10) {
  ["id"]=>
  int(2)
  ["user_id"]=>
  int(1)
  ["title"]=>
  string(5) "test2"
  ["created_at"]=>
  string(19) "2024-01-13 10:06:04"
  ["updated_at"]=>
  string(16) "2024-03-25 09:48"
  ["deleted_at"]=>
  NULL
  ["pv_num"]=>
  int(0)
  ["intro"]=>
  string(5) "test2"
  ["content"]=>
  NULL
  ["is_top"]=>
  int(1)
}

1.5 源码

转换类型

php 复制代码
#Hyperf\Database\Model\Concerns\HasAttributes
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);
    }
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 asDecimal($value, $decimals)
    {
        return number_format((float) $value, (int) $decimals, '.', '');
    }
protected function isDateCastable($key)
    {
        return $this->hasCast($key, ['date', 'datetime']);
    }
protected function isJsonCastable($key)
    {
        return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
    }
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;
    }

数据更新

php 复制代码
#Hyperf\Database\Model\Concerns\HasAttributes
public function syncAttributes()
    {
        $this->mergeAttributesFromClassCasts();
        return $this;
    }
protected function mergeAttributesFromClassCasts()
    {
        foreach ($this->classCastCache as $key => $value) {
            if ($value instanceof Synchronized && $value->isSynchronized()) {
                continue;
            }

            $caster = $this->resolveCasterClass($key);

            $this->attributes = array_merge(
                $this->attributes,
                $caster instanceof CastsInboundAttributes
                ? [$key => $value]
                : $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes))
            );
        }
    }

二 数组和json转换

数据库存json,数据库字段为text或json,数据获取时转变为数组。

获取时使用 Hyperf\Database\Model\Model::__get()调用Hyperf\Database\Model\Concerns\HasAttributes::getAttribute()。

设置时使用Model::__set()调用HasAttributes::setAttribute()。

判断是否可为json数据,则使用json_encode()转化为json数据保存。

根据源码,HasAttributes::casts数组中设置字段类型为array, json, object, collection中的类型,可为json字符串保存。

2.1 测试

php 复制代码
#App\Controller\TestController
public function testmodifier() { 
        $article = Article::query()->find(2);
        $options = $article->content;
        var_dump($options);
        if (empty($options)) {
            $article->content = ['intro' => 'test', 'content' => 'test1'];
            $article->save();
            $options = $article->content;
        }
        var_dump($options);
}

测试结果

php 复制代码
NULL
array(2) {
  ["intro"]=>
  string(4) "test"
  ["content"]=>
  string(5) "test1"
}
sql 复制代码
select content from articles where id=2


{"intro":"test","content":"test1"}

2.2 源码

getAttribute()源码详见1.5源码。

php 复制代码
#Hyperf\Database\Model\Concerns\HasAttributes
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;
    }
protected function isJsonCastable($key) {
        return $this->hasCast($key, ['array', 'json', 'object', 'collection']);
    }
public function hasCast($key, $types = null) {
        if (array_key_exists($key, $this->getCasts())) {
            return $types ? in_array($this->getCastType($key), (array) $types, true) : true;
        }

        return false;
    }
public function getCasts() {
        if ($this->getIncrementing()) {
            return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
        }

        return $this->casts;
    }
protected function castAttributeAsJson($key, $value) {
        $value = $this->asJson($value);

        if ($value === false) {
            throw JsonEncodingException::forAttribute(
                $this,
                $key,
                json_last_error_msg()
            );
        }

        return $value;
    }

三 Date类型转换

在设置的时候,若不是Hyperf\Database\Model\Model::CREATED_AT,Model::UPDATED_AT,即字段不为created_at或updated_at,且设置参数如日期格式,则不会调用转换。

因为判断是否可转换的时候被过滤掉。像官网设置created_at不会被转换,可能和版本有关系。

再说获取。

直接获取对应属性,即运行Hyperf\Database\Model\Model::__get()。 转换类型会被判断为custom_datetime,转换为Carbon类。

但是设置的格式未被解析,还是需要手动设置格式。使用Hyperf\Database\Model\Concerns\HasAttributes::getAttributes()获取,直接获取HasAttributes::attributes属性。

这两种对与设置的格式都不会解析,返回都是根据HasAttributes::dateFormat,其默认值都是Y-m-d H:i:s。

使用HasAttributes::attributesToArray(),会调用HasAttributes::addCastAttributesToArray(),会判断是否为custom_datetime。

是custom_datetime类型,则会调用php系统类自带函数format(),通过解析参数,获得格式化数据。

其实和自己使用format(),运行原理一样,效果也一样。

3.1 测试

php 复制代码
#App\Controller\TestController
public function testmodifier() {
    $pr = PushRecode::query()->find(1);
    $pr->push_time = date('Y-m-d H:i:s');
    var_dump($pr->push_time);
    $pr = $pr->syncAttributes();
    var_dump($pr->push_time->format('Y-m-d H:00:00'));
    $pr->created_at = date('Y-m-d H:i:s');
    $info = $pr->syncAttributes()->getAttributes();
    var_dump($info);
    $info = $pr->syncAttributes()->attributesToArray();
    var_dump($info);
}

测试结果

php 复制代码
object(Carbon\Carbon)#1151 (19) {
  ["endOfTime":protected]=>
  bool(false)
  ["startOfTime":protected]=>
  bool(false)
  ["constructedObjectId":protected]=>
  string(32) "00000000607fdc84000000003fa11939"
  ["localMonthsOverflow":protected]=>
  NULL
  ["localYearsOverflow":protected]=>
  NULL
  ["localStrictModeEnabled":protected]=>
  NULL
  ["localHumanDiffOptions":protected]=>
  NULL
  ["localToStringFormat":protected]=>
  NULL
  ["localSerializer":protected]=>
  NULL
  ["localMacros":protected]=>
  NULL
  ["localGenericMacros":protected]=>
  NULL
  ["localFormatFunction":protected]=>
  NULL
  ["localTranslator":protected]=>
  NULL
  ["dumpProperties":protected]=>
  array(3) {
    [0]=>
    string(4) "date"
    [1]=>
    string(13) "timezone_type"
    [2]=>
    string(8) "timezone"
  }
  ["dumpLocale":protected]=>
  NULL
  ["dumpDateProperties":protected]=>
  NULL
  ["date"]=>
  string(26) "2024-04-11 09:30:03.000000"
  ["timezone_type"]=>
  int(3)
  ["timezone"]=>
  string(3) "UTC"
}
string(19) "2024-04-11 09:00:00"
array(4) {
  ["id"]=>
  int(1)
  ["is_push"]=>
  int(1)
  ["push_time"]=>
  string(19) "2024-04-11 09:30:03"
  ["created_at"]=>
  string(19) "2024-04-11 09:30:03"
}
array(4) {
  ["id"]=>
  int(1)
  ["is_push"]=>
  int(1)
  ["push_time"]=>
  string(10) "2024-04-11"
  ["created_at"]=>
  string(10) "2024-04-11"
}

3.2 源码

Hyperf\Database\Model\Model::getAttribute()内容详见1.5源码。

Hyperf\Database\Model\Model::setAttribute()内容详见2.2源码。

php 复制代码
#Hyperf\Database\Model\Concerns\HasAttributes
protected function isClassCastable($key) {
        return array_key_exists($key, $this->getCasts())
        && class_exists($class = $this->parseCasterClass($this->getCasts()[$key]))
        && !in_array($class, static::$primitiveCastTypes);
    }
protected function parseCasterClass($class) {
        return strpos($class, ':') === false
        ? $class
        : explode(':', $class, 2)[0];
    }
protected static $primitiveCastTypes = [
        'array',
        'bool',
        'boolean',
        'collection',
        'custom_datetime',
        'date',
        'datetime',
        'decimal',
        'double',
        'float',
        'int',
        'integer',
        'json',
        'object',
        'real',
        'string',
        'timestamp',
    ];
public function getCasts() {
        if ($this->getIncrementing()) {
            return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
        }

        return $this->casts;
    }

public function getAttributes() {
        return $this->attributes;
    }


protected function isDateAttribute($key) {
        return in_array($key, $this->getDates(), true)
        || $this->isDateCastable($key);
    }
protected function isDateCastable($key) {
        return $this->hasCast($key, ['date', 'datetime']);
    }


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 getCastType($key) {
        if ($this->isCustomDateTimeCast($this->getCasts()[$key])) {
            return 'custom_datetime';
        }

        if ($this->isDecimalCast($this->getCasts()[$key])) {
            return 'decimal';
        }

        return trim(strtolower($this->getCasts()[$key]));
    }
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);
    }
protected function isCustomDateTimeCast($cast) {
        return strncmp($cast, 'date:', 5) === 0
        || strncmp($cast, 'datetime:', 9) === 0;
    }

根据源码,判断array_key_exists(key, this->getCasts())、class_exists(class = this->parseCasterClass(this-\>getCasts()\[key]))应为true,!in_array(class, static::primitiveCastTypes)为false,所以isClassCastable()为false。

isDateAttribute()结果应该也为false。

之后执行getAttributeValue(),获取castType为custom_datetime,最终转换为Carbon\Carbon类。

php 复制代码
#Hyperf\Database\Model\Concerns\HasAttributes
public function attributesToArray() {
        // If an attribute is a date, we will cast it to a string after converting it
        // to a DateTime / Carbon instance. This is so we will get some consistent
        // formatting while accessing attributes vs. arraying / JSONing a model.
        $attributes = $this->addDateAttributesToArray(
            $attributes = $this->getArrayableAttributes()
        );

        $attributes = $this->addMutatedAttributesToArray(
            $attributes,
            $mutatedAttributes = $this->getMutatedAttributes()
        );

        // Next we will handle any casts that have been setup for this model and cast
        // the values to their appropriate type. If the attribute has a mutator we
        // will not perform the cast on those attributes to avoid any confusion.
        $attributes = $this->addCastAttributesToArray(
            $attributes,
            $mutatedAttributes
        );

        // Here we will grab all of the appended, calculated attributes to this model
        // as these attributes are not really in the attributes array, but are run
        // when we need to array or JSON the model for convenience to the coder.
        foreach ($this->getArrayableAppends() as $key) {
            $attributes[$key] = $this->mutateAttributeForArray($key, null);
        }

        return $attributes;
    }
protected function addCastAttributesToArray(array $attributes, array $mutatedAttributes) {
        foreach ($this->getCasts() as $key => $value) {
            if (!array_key_exists($key, $attributes) || in_array($key, $mutatedAttributes)) {
                continue;
            }

            // Here we will cast the attribute. Then, if the cast is a date or datetime cast
            // then we will serialize the date for the array. This will convert the dates
            // to strings based on the date format specified for these Model models.
            $attributes[$key] = $this->castAttribute(
                $key,
                $attributes[$key]
            );

            // If the attribute cast was a date or a datetime, we will serialize the date as
            // a string. This allows the developers to customize how dates are serialized
            // into an array without affecting how they are persisted into the storage.
            if ($attributes[$key]
                && ($value === 'date' || $value === 'datetime')) {
                $attributes[$key] = $this->serializeDate($attributes[$key]);
            }

            if ($attributes[$key] && $this->isCustomDateTimeCast($value)) {
                $attributes[$key] = $attributes[$key]->format(explode(':', $value, 2)[1]);
            }

            if ($attributes[$key] instanceof Arrayable) {
                $attributes[$key] = $attributes[$key]->toArray();
            }
        }

        return $attributes;
    }

官网打不开了......后续内容等官网好了再说吧......

相关推荐
BingoGo18 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php
JaguarJack18 小时前
OpenSwoole 26.2.0 发布:支持 PHP 8.5、io_uring 后端及协程调试改进
后端·php·服务端
JaguarJack2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
后端·php·服务端
BingoGo2 天前
推荐 PHP 属性(Attributes) 简洁读取 API 扩展包
php
JaguarJack3 天前
告别 Laravel 缓慢的 Blade!Livewire Blaze 来了,为你的 Laravel 性能提速
后端·php·laravel
郑州光合科技余经理3 天前
代码展示:PHP搭建海外版外卖系统源码解析
java·开发语言·前端·后端·系统架构·uni-app·php
QQ5110082853 天前
python+springboot+django/flask的校园资料分享系统
spring boot·python·django·flask·node.js·php
WeiXin_DZbishe3 天前
基于django在线音乐数据采集的设计与实现-计算机毕设 附源码 22647
javascript·spring boot·mysql·django·node.js·php·html5
longxiangam4 天前
Composer 私有仓库搭建
php·composer
上海云盾-高防顾问4 天前
DNS异常怎么办?快速排查+解决指南
开发语言·php