Özniteliklere giriş
(PHP 8)
Öznitelikler, koddaki bildirimlerin üzerine yapılandırılmış, makine
tarafından okunabilir meta veri bilgilerinin eklenmesini sağlar:
Sınıflar, yöntemler, işlevler, bağımsız değişkenler, özellikler ve sınıf
sabitleri bir özniteliğin hedefi olabilir. Özniteliklerle tanımlanan
meta veriler daha sonra Yansıtma
Arayüzleri kullanılarak çalışma anında incelenebilir.
Bu nedenle öznitelikler, doğrudan koda gömülmüş bir yapılandırma dili
olarak düşünülebilir.
Özniteliklerle, bir özelliğin kapsamlı gerçeklenimi ile bir
uygulamadaki somut kullanımı ayrıştırılabilir. Bir bakıma arayüzler,
gerçeklenimleri ile karşılaştırılabilir. Ancak arayüzlerin ve
gerçeklenimlerin kodla ilgili olduğu yerlerde öznitelikler, ek bilgi
ve yapılandırmaya küçük açıklamalar eklemekle ilgilidir. Arayüzler
sınıflarla gerçeklenebilir, ancak öznitelikler ek olarak yöntemler,
işlevler, bağımsız değişkenler, özellikler ve sınıf sabitleri üzerinde de
bildirilebilir. Bu nedenle arayüzlerden daha esnektir.
Öznitelik kullanımına basit bir örnek, öznitelikleri kullanmak için isteğe
bağlı yöntemlere sahip bir arayüzü dönüştürmektir.
ActionHandler
arayüzünün bir uygulamadaki bir işlemi
yerine getirdiğini ve bu arayüzün bazı gerçeklenimlerinin ilklendirilmesi
gerekirken diğerlerine bunun gerekmediğini varsayalım.
ActionHandler
arayüzünü gerçekleyen tüm sınıfların
setUp()
yöntemini gerçeklemesi gerekirken, bunu
yapmak yerine, onun yerini alabilecek bir öznitelik kullanılabilir.
Bu yaklaşımın tek yararı, öznitelliğin birkaç kez kullanabilmesidir.
Örnek 1 - Bir arayüzün isteğe bağlı yöntemlerini özniteliklerle gerçeklemek
<?php
interface ActionHandler
{
public function execute();
}
#[Attribute]
class SetUp {}
class CopyFile implements ActionHandler
{
public string $fileName;
public string $targetDirectory;
#[SetUp]
public function fileExists()
{
if (!file_exists($this->fileName)) {
throw new RuntimeException("Dosya yok");
}
}
#[SetUp]
public function targetDirectoryExists()
{
if (!file_exists($this->targetDirectory)) {
mkdir($this->targetDirectory);
} elseif (!is_dir($this->targetDirectory)) {
throw new RuntimeException("Hedef dizin $this->targetDirectory bir dizin değil");
}
}
public function execute()
{
copy($this->fileName, $this->targetDirectory . '/' . basename($this->fileName));
}
}
function executeAction(ActionHandler $actionHandler)
{
$reflection = new ReflectionObject($actionHandler);
foreach ($reflection->getMethods() as $method) {
$attributes = $method->getAttributes(SetUp::class);
if (count($attributes) > 0) {
$methodName = $method->getName();
$actionHandler->$methodName();
}
}
$actionHandler->execute();
}
$copyAction = new CopyFile();
$copyAction->fileName = "/tmp/foo.jpg";
$copyAction->targetDirectory = "/home/user";
executeAction($copyAction);
Harshdeep ¶3 years ago
While the example displays us what we can accomplish with attributes, it should be kept in mind that the main idea behind attributes is to attach static metadata to code (methods, properties, etc.). This metadata often includes concepts such as "markers" and "configuration". For example, you can write a serializer using reflection that only serializes marked properties (with optional configuration, such as field name in serialized file). This is reminiscent of serializers written for C# applications.That said, full reflection and attributes go hand in hand. If your use case is satisfied by inheritance or interfaces, prefer that. The most common use case for attributes is when you have no prior information about the provided object/class.<?phpinterface JsonSerializable{ public function toJson() : array;}?>versus, using attributes,<?php#[Attribute]class JsonSerialize { public function __constructor(public ?string $fieldName = null) {}}class VersionedObject{ #[JsonSerialize] public const version = '0.0.1';}public class UserLandClass extends VersionedObject{ #[JsonSerialize('call it Jackson')] public string $myValue;}?>The example above is a little extra convoluted with the existence of the VersionedObject class as I wished to display that with attribute mark ups, you do not need to care how the base class manages its attributes (no call to parent in overriden method).
Florian Krmer ¶2 years ago
I've tried Harshdeeps example and it didn't run out of the box and I think it is not complete, so I wrote a complete and working naive example regarding attribute based serialization.<?phpdeclare(strict_types=1);#[Attribute(Attribute::TARGET_CLASS_CONSTANT|Attribute::TARGET_PROPERTY)]class JsonSerialize{ public function __construct(public ?string $fieldName = null) {}}class VersionedObject{ #[JsonSerialize] public const version = '0.0.1';}class UserLandClass extends VersionedObject{ protected string $notSerialized = 'nope'; #[JsonSerialize('foobar')] public string $myValue = ''; #[JsonSerialize('companyName')] public string $company = ''; #[JsonSerialize('userLandClass')] protected ?UserLandClass $test; public function __construct(?UserLandClass $userLandClass = null) { $this->test = $userLandClass; }}class AttributeBasedJsonSerializer { protected const ATTRIBUTE_NAME = 'JsonSerialize'; public function serialize($object) { $data = $this->extract($object); return json_encode($data, JSON_THROW_ON_ERROR); } protected function reflectProperties(array $data, ReflectionClass $reflectionClass, object $object) { $reflectionProperties = $reflectionClass->getProperties(); foreach ($reflectionProperties as $reflectionProperty) { $attributes = $reflectionProperty->getAttributes(static::ATTRIBUTE_NAME); foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); $name = $instance->fieldName ?? $reflectionProperty->getName(); $value = $reflectionProperty->getValue($object); if (is_object($value)) { $value = $this->extract($value); } $data[$name] = $value; } } return $data; } protected function reflectConstants(array $data, ReflectionClass $reflectionClass) { $reflectionConstants = $reflectionClass->getReflectionConstants(); foreach ($reflectionConstants as $reflectionConstant) { $attributes = $reflectionConstant->getAttributes(static::ATTRIBUTE_NAME); foreach ($attributes as $attribute) { $instance = $attribute->newInstance(); $name = $instance->fieldName ?? $reflectionConstant->getName(); $value = $reflectionConstant->getValue(); if (is_object($value)) { $value = $this->extract($value); } $data[$name] = $value; } } return $data; } protected function extract(object $object) { $data = []; $reflectionClass = new ReflectionClass($object); $data = $this->reflectProperties($data, $reflectionClass, $object); $data = $this->reflectConstants($data, $reflectionClass); return $data; }}$userLandClass = new UserLandClass();$userLandClass->company = 'some company name';$userLandClass->myValue = 'my value';$userLandClass2 = new UserLandClass($userLandClass);$userLandClass2->company = 'second';$userLandClass2->myValue = 'my second value';$serializer = new AttributeBasedJsonSerializer();$json = $serializer->serialize($userLandClass2);var_dump(json_decode($json, true));