[Apex] Schema名前空間

# Schema名前空間
Schema名前空間はのSObjectや項目の設定情報をApexなどで使用するために使用されます。

## 主なSchema名前空間のクラス
* <b>トークン(SObjectType、SObjectTypeField)について</b> ― SObjectまたは項目への参照で、メタデータの設定に関する情報は持っていません。そのためとても軽量に扱えます[^1]。`getDescribe()`メソッドを取得することで、メタデータ情報を含むオブジェクト(DescribeSObjectResult、DescribeFieldResult)のインスタンスが取得できます。トークンはコンパイル時に検証されるため、存在しないSObject、項目の場合はコンパイルエラーとなります。
* <b>DescribeSObjectResultについて</b> ― SObjectの詳細情報を取得できます。例えば、全項目のマップ、レコードタイプ、現在のユーザのオブジェクトへのアクセス権などです。
* <b>DescribeFieldResultについて</b> ― 項目の詳細情報を取得できます。例えば、項目型、最大文字数、デフォルト値、現在のユーザの項目へのアクセス権などです。

[^1]: インスタンス作成前後のヒープサイズの差を取得することでサイズを確認できます。Limits.getHeapSize()

| クラス | 説明 | 使用例 |
|:--|:--|:--|
| [SObjectType](#sobjecttype) | SObjectのトークン | ・DescribeSObjectResultの取得 |
| [DescribeSObjectResult](#describesobjectresult) | SObjectの詳細 | ・ラベル名の取得<br>・動的クエリの作成<br>・現在のユーザのオブジェクトへのアクセス権の参照 |
| [SObjectField](#sobjectfield) | 項目のトークン | ・DescribeFieldResultの取得<br>・レコードの動的な値の設定  |
| [DescribeFieldResult](#describefieldresult) | 項目の詳細 | ・ラベル名の取得<br>・動的クエリの作成<br>・現在のユーザの項目へのアクセス権の参照 |
| FieldSet | 項目セットの詳細 | ・FieldSetMemberの取得<br>・Visualforceで表示する項目に項目セットを使用する |
| FieldSetMember | 項目セットに含まれる各項目の詳細 | ・DBではなく項目セットで定義した必須/非必須を使用する |
| RecordTypeInfo | レコードタイプの詳細 | ・レコードタイプIdの取得<br>・現在のユーザのレコードタイプ使用可否を参照 |
| ChildRelationship | 子リレーションの詳細 | ・子リレーションを含んだ動的クエリの作成 |
| PicklistEntry | 選択リスト値の詳細 | ・Visualforceで項目定義からピックアップリストを作成する |
| DescribeTabSetResult | アプリケーションの詳細 | ・DescribeTabResultの取得 |
| DescribeTabResult | タブの詳細 | ・タブに設定したアイコンを取得する |

## System名前空間のSchemaクラス
SchemaクラスはDescribe情報を取得する静的メソッドが用意されています。
参考: [Schema クラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.224.0.apexcode.meta/apexcode/apex_methods_system_schema.htm)

* <b>`getGlobalDescribe()`メソッド</b> ― すべてのSObjectのトークンを取得します。戻り値の型は`Map<String, Schema.SObjectType>`で、キーはAPI参照名のStringです。
* <b>`describeSObjects(sObjectTypes)`メソッド</b> ― SObjectのAPI参照名のリストを引数にして、DescribeSObjectResultのリストを返します。Schema.SObjectTypeオブジェクトの`getDescribe()`メソッドとは異なり、動的にSObjectを指定して、一度に複数のDescribeSObjectResultを取得できます。
* <b>`describeTabs()`メソッド</b> ― 実行ユーザが利用可能な標準アプリケーションとカスタムアプリケーションの情報を取得します。戻り値はSchema.DescribeTabSetResultのリストです。

# Apexでの使用
Schema名前空間とSystem名前空間のクラスは暗黙的にインポートされるため、`Schema.`と`System.`は省略可能です。実際に現場では省略することも多いです。
ですが、Schema名前空間なのか、System名前空間のSchemaクラスなのかわかりやすくする目的でここでは省略せずに記述します。

また、使用するたびに調べ直すことがないように、エディタのスニペットに登録しておくのをおすすめします → [例](#小ネタ)。


<!-- ********************** -->
<!-- オブジェクト               -->
<!-- ********************** -->
## SObjectType
SObjectTypeはSObjectのトークンです。
参考: [SObjectType クラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_Schema_SObjectType.htm)

### 取得
```apex:SObjectTypeの取得
// ① 静的に取得
SObjectType token1 = Schema.Account.sObjectType; // getSObjectType()でも取得できます。

// ② System.Schemaクラスを使って全オブジェクトトークンを取得
Map<String, Schema.SObjectType> tokenMap = System.Schema.getGlobalDescribe();
SObjectType token2 = tokenMap.get('Account');

// ③ SObjectのインスタンスから取得
// ③-1 SObjectのインスタンスから
Account obj = new Account();
SObjectType token3_1 = obj.getSObjectType(); 
// ③-2 List<SObject>のインスタンスから
List<Account> objList = new List<Account>();
SObjectType token3_2 = objList.getSObjectType(); 

// ④ Idから取得
Id accId = Id.valueOf('0012x000009UhXQ');
SObjectType token4 = accId.getSObjectType();
```

## DescribeSObjectResult 
SObjectのメタデータ情報を利用するために使用されるクラスです。
参考: [DescribeSObjectResult クラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_sobject_describe.htm)

### 取得
```apex:DescribeSObjectResultの取得
// ① 静的に取得
DescribeSObjectResult dsr1 = Schema.SObjectType.Account;

// ② SObjectTypeから取得
SObjectType token = Schema.Account.sObjectType;
DescribeSObjectResult dsr2 = token.getDescribe();

// ③ System.Schemaクラスをつかって指定したSObjectの情報を一括で取得
List<String> objectNames = new List<String>{'Account', 'Contact'};
List<Schema.DescribeSObjectResult > sObjectTypes = System.Schema.describeSObjects(objectNames);
DescribeSObjectResult dsr3 = sObjectTypes[0];
```

### 使用例
```apex
DescribeSObjectResult dsr = Schema.SObjectType.Account;

// 現在のユーザのオブジェクトレベル権限を確認する
Boolean isAccessible = dsr.isAccessible();
Boolean isCreateable = dsr.isCreateable();
Boolean isUpdatable  = dsr.isUpdateable();
Boolean isDeletable  = dsr.isDeletable();
```


<!-- ********************** -->
<!-- 項目                    -->
<!-- ********************** -->
## SObjectField
SObjectFieldは項目のトークンです。
参考: [SObjectField クラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_class_Schema_SObjectField.htm)

### 取得
```apex:SObjectFieldの取得
// ① 静的に取得
SObjectField field1 = Schema.Account.Name;

// ② DescribeSObjectResultから全項目トークンを取得
DescribeSObjectResult dsr = Schema.SObjectType.Account;
Map<String, Schema.SObjectField> fieldMap = dsr.fields.getMap();
SObjectField field2 = fieldMap.get('name');
```

### 使用例
```apex
SObjectField field = Schema.Account.Name;

// レコードに値を設定・取得する
Account obj = new Account();
obj.put(field, 'Salesforce');
Object value = obj.get(field);
```

## DescribeFieldResult
項目のメタデータ情報を利用するために使用されるクラスです。
参考: [DescribeFieldResult クラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_methods_system_fields_describe.htm)

### 取得
```apex:DescribeFieldResult
// ① 静的に取得
DescribeFieldResult dfr1 = Schema.SObjectType.Account.fields.Name;

// ② SObjectFieldから取得
SObjectField field = Schema.Account.Name;
DescribeFieldResult dfr2 = field.getDescribe();
```

# Schema名前空間のユースケース
選択リスト項目のラベル名とAPI参照名のマップを取得する

### 項目セットから項目のリストを取得する
```apex
/** 項目セットをトークンに変換 */
public static List<Schema.SObjectField> toFieldList(Schema.FieldSet fs) {
    List<Schema.SObjectField> fields = new List<Schema.SObjectField>();
    for (Schema.FieldSetMember fsm : fs.getFields()) {
        fields.add(fsm.getSObjectField());
    }
    return fields;
}

// 使用例
List<Schema.SObjectField> fields = toFieldList(Schema.SObjectType.Account.FieldSets.FieldSetApiName);
```

### 選択リスト項目から選択肢(SelectOption)を取得する
[SelectOptionクラス | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_pages_selectoption.htm)

```apex
/** 選択リスト取得メソッド */
public static List<SelectOption> getSelectOptions(Schema.SObjectField field) {
    List<SelectOption> soList = new List<SelectOption>();
    for (Schema.PicklistEntry pe : field.getDescribe().getPickListValues()) {
        if (!pe.isActive()) { continue; }
        soList.add(new SelectOption(pe.getValue(), pe.getLabel()));
    }
    return soList;
}
```

### レコードタイプIDを取得する
```apex
// API参照名を使用してレコードタイプIDを取得
Id recordTypeId1 = Schema.SObjectType.Account.getRecordTypeInfosByDeveloperName().get('RecordTypeName').getRecordTypeId();
// ラベル名を使用してレコードタイプIDを取得
Id recordTypeId2 = Schema.SObjectType.Account.getRecordTypeInfosByName().get('レコードタイプ名').getRecordTypeId();
```

# 小ネタ
エディタのスニペットに登録しておくと、ストレスなくSchema名前空間のクラスが扱えるので、おすすめです。

```json:apex.json
  "FieldSet": {
    "prefix": ["fieldset","項目セット"],
    "body": ["Schema.SObjectType.${1:sobject_name}.fieldSets.${2:field_set_name}"],
    "description": "項目セットの使用"
  }
```

# 参考
* [Apex Describe Information について | Apex開発者ガイド](https://developer.salesforce.com/docs/atlas.ja-jp.apexcode.meta/apexcode/apex_dynamic_describe_objects_understanding.htm)
* [What is Describe object,Schema and Token | Stack Exchange](https://salesforce.stackexchange.com/questions/23031/what-is-describe-object-schema-and-token)