ABAP Dynamic Programming : RTTS, CREATE DATA, Reflection

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

La Programmation dynamique permet la création et la manipulation d’objets de données à l’exécution. Avec RTTS (Runtime Type Services) et des instructions dynamiques, des programmes flexibles et génériques peuvent être développés.

Classes RTTS

ClasseDescription
CL_ABAP_TYPEDESCRClasse de base pour les descriptions de types
CL_ABAP_ELEMDESCRTypes élémentaires (i, string, etc.)
CL_ABAP_STRUCTDESCRStructures
CL_ABAP_TABLEDESCRTables internes
CL_ABAP_REFDESCRRéférences
CL_ABAP_CLASSDESCRClasses
CL_ABAP_OBJECTDESCRObjets

Exemples

1. Déterminer le type d’un objet de données

DATA: lv_string TYPE string VALUE 'Test',
lt_table TYPE TABLE OF mara,
lo_struct TYPE REF TO kna1.
" Déterminer le type
DATA(lo_type1) = cl_abap_typedescr=>describe_by_data( lv_string ).
DATA(lo_type2) = cl_abap_typedescr=>describe_by_data( lt_table ).
DATA(lo_type3) = cl_abap_typedescr=>describe_by_name( 'KNA1' ).
WRITE: / 'Type String:', lo_type1->type_kind,
/ 'Type Table:', lo_type2->type_kind,
/ 'Type KNA1:', lo_type3->type_kind.
" Valeurs type_kind :
" C = Character, N = Numeric, I = Integer, P = Packed
" D = Date, T = Time, X = Hex, F = Float
" g = String, h = Table, u = Structure, v = Class

2. Lire les composants d’une structure

DATA: ls_customer TYPE kna1.
" Décrire la structure
DATA(lo_struct) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_data( ls_customer )
).
" Afficher tous les composants
LOOP AT lo_struct->components INTO DATA(ls_comp).
WRITE: / ls_comp-name, ls_comp-type_kind, ls_comp-length.
ENDLOOP.
" Rechercher un composant spécifique
READ TABLE lo_struct->components INTO ls_comp
WITH KEY name = 'KUNNR'.
IF sy-subrc = 0.
WRITE: / 'KUNNR trouvé, longueur:', ls_comp-length.
ENDIF.

3. Créer une structure dynamique

" Définir les composants
DATA(lt_components) = VALUE cl_abap_structdescr=>component_table(
( name = 'FIELD1' type = cl_abap_elemdescr=>get_c( 10 ) )
( name = 'FIELD2' type = cl_abap_elemdescr=>get_i( ) )
( name = 'FIELD3' type = cl_abap_elemdescr=>get_string( ) )
( name = 'FIELD4' type = cl_abap_elemdescr=>get_d( ) )
).
" Créer le type de structure
DATA(lo_struct_type) = cl_abap_structdescr=>create( lt_components ).
" Créer l'objet de données
DATA: lr_struct TYPE REF TO data.
CREATE DATA lr_struct TYPE HANDLE lo_struct_type.
" Remplir avec des données
ASSIGN lr_struct->* TO FIELD-SYMBOL(<fs_struct>).
ASSIGN COMPONENT 'FIELD1' OF STRUCTURE <fs_struct> TO FIELD-SYMBOL(<fv_field1>).
<fv_field1> = 'Bonjour'.
ASSIGN COMPONENT 'FIELD2' OF STRUCTURE <fs_struct> TO FIELD-SYMBOL(<fv_field2>).
<fv_field2> = 42.

4. Créer une table interne dynamique

" Type de structure (comme ci-dessus)
DATA(lt_components) = VALUE cl_abap_structdescr=>component_table(
( name = 'KUNNR' type = cl_abap_elemdescr=>get_c( 10 ) )
( name = 'NAME1' type = cl_abap_elemdescr=>get_c( 35 ) )
( name = 'AMOUNT' type = cl_abap_elemdescr=>get_p( p_length = 8 p_decimals = 2 ) )
).
DATA(lo_struct_type) = cl_abap_structdescr=>create( lt_components ).
" Créer le type de table
DATA(lo_table_type) = cl_abap_tabledescr=>create(
p_line_type = lo_struct_type
p_table_kind = cl_abap_tabledescr=>tablekind_std
).
" Créer la table
DATA: lr_table TYPE REF TO data.
CREATE DATA lr_table TYPE HANDLE lo_table_type.
ASSIGN lr_table->* TO FIELD-SYMBOL(<ft_table>).
" Ajouter une ligne
DATA: lr_line TYPE REF TO data.
CREATE DATA lr_line TYPE HANDLE lo_struct_type.
ASSIGN lr_line->* TO FIELD-SYMBOL(<fs_line>).
ASSIGN COMPONENT 'KUNNR' OF STRUCTURE <fs_line> TO FIELD-SYMBOL(<fv>).
<fv> = '0000001000'.
ASSIGN COMPONENT 'NAME1' OF STRUCTURE <fs_line> TO <fv>.
<fv> = 'Client Test'.
ASSIGN COMPONENT 'AMOUNT' OF STRUCTURE <fs_line> TO <fv>.
<fv> = '1234.56'.
INSERT <fs_line> INTO TABLE <ft_table>.

5. SELECT dynamique

DATA: lv_tablename TYPE tabname VALUE 'KNA1',
lv_fields TYPE string VALUE 'KUNNR NAME1 ORT01',
lv_where TYPE string VALUE 'LAND1 = ''FR''',
lr_data TYPE REF TO data.
" Déterminer la structure de la table
DATA(lo_table_descr) = CAST cl_abap_tabledescr(
cl_abap_tabledescr=>describe_by_name( lv_tablename )
).
DATA(lo_line_type) = lo_table_descr->get_table_line_type( ).
" Créer une table dynamique
DATA(lo_dyn_table) = cl_abap_tabledescr=>create( lo_line_type ).
CREATE DATA lr_data TYPE HANDLE lo_dyn_table.
ASSIGN lr_data->* TO FIELD-SYMBOL(<ft_result>).
" SELECT dynamique
SELECT (lv_fields)
FROM (lv_tablename)
WHERE (lv_where)
INTO CORRESPONDING FIELDS OF TABLE @<ft_result>
UP TO 100 ROWS.
" Traiter le résultat
LOOP AT <ft_result> ASSIGNING FIELD-SYMBOL(<fs_row>).
ASSIGN COMPONENT 'KUNNR' OF STRUCTURE <fs_row> TO FIELD-SYMBOL(<fv_kunnr>).
ASSIGN COMPONENT 'NAME1' OF STRUCTURE <fs_row> TO FIELD-SYMBOL(<fv_name>).
WRITE: / <fv_kunnr>, <fv_name>.
ENDLOOP.

6. Liste de champs générique

CLASS zcl_dynamic_fields DEFINITION.
PUBLIC SECTION.
METHODS: read_table_dynamic
IMPORTING iv_table TYPE tabname
it_fields TYPE string_table
iv_where TYPE string OPTIONAL
EXPORTING et_data TYPE REF TO data.
ENDCLASS.
CLASS zcl_dynamic_fields IMPLEMENTATION.
METHOD read_table_dynamic.
" Liste de champs comme string
DATA(lv_fields) = concat_lines_of( table = it_fields sep = ` ` ).
" Créer une structure uniquement avec les champs souhaités
DATA(lo_struct) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_name( iv_table )
).
DATA(lt_components) = VALUE cl_abap_structdescr=>component_table( ).
LOOP AT it_fields INTO DATA(lv_field).
READ TABLE lo_struct->components INTO DATA(ls_comp)
WITH KEY name = to_upper( lv_field ).
IF sy-subrc = 0.
APPEND VALUE #(
name = ls_comp-name
type = lo_struct->get_component_type( ls_comp-name )
) TO lt_components.
ENDIF.
ENDLOOP.
" Créer une table dynamique
DATA(lo_new_struct) = cl_abap_structdescr=>create( lt_components ).
DATA(lo_new_table) = cl_abap_tabledescr=>create( lo_new_struct ).
CREATE DATA et_data TYPE HANDLE lo_new_table.
ASSIGN et_data->* TO FIELD-SYMBOL(<ft_data>).
" SELECT
IF iv_where IS INITIAL.
SELECT (lv_fields) FROM (iv_table)
INTO CORRESPONDING FIELDS OF TABLE @<ft_data>.
ELSE.
SELECT (lv_fields) FROM (iv_table)
WHERE (iv_where)
INTO CORRESPONDING FIELDS OF TABLE @<ft_data>.
ENDIF.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA: lr_result TYPE REF TO data.
NEW zcl_dynamic_fields( )->read_table_dynamic(
EXPORTING
iv_table = 'MARA"
it_fields = VALUE #( ( `MATNR` ) ( `MTART` ) ( `MATKL` ) )
iv_where = 'MTART = ''FERT''"
IMPORTING
et_data = lr_result
).
ASSIGN lr_result->* TO FIELD-SYMBOL(<ft_materials>).

7. Instanciation dynamique d’objet

DATA: lv_classname TYPE seoclsname VALUE 'ZCL_MY_CLASS',
lo_object TYPE REF TO object.
" Instancier la classe dynamiquement
CREATE OBJECT lo_object TYPE (lv_classname).
" Avec paramètres
DATA: lt_params TYPE abap_parmbind_tab.
lt_params = VALUE #(
( name = 'IV_PARAM1' kind = cl_abap_objectdescr=>exporting value = REF #( 'Valeur1' ) )
( name = 'IV_PARAM2' kind = cl_abap_objectdescr=>exporting value = REF #( 123 ) )
).
CREATE OBJECT lo_object TYPE (lv_classname)
PARAMETER-TABLE lt_params.

8. Appel de méthode dynamique

DATA: lo_object TYPE REF TO object,
lv_method TYPE string VALUE 'PROCESS_DATA',
lt_params TYPE abap_parmbind_tab,
lt_results TYPE abap_parmbind_tab,
lv_input TYPE string VALUE 'Test',
lv_output TYPE string.
" Créer l'objet
CREATE OBJECT lo_object TYPE zcl_processor.
" Construire les paramètres
lt_params = VALUE #(
( name = 'IV_INPUT"
kind = cl_abap_objectdescr=>exporting
value = REF #( lv_input ) )
).
lt_results = VALUE #(
( name = 'RV_OUTPUT"
kind = cl_abap_objectdescr=>returning
value = REF #( lv_output ) )
).
" Appeler la méthode
CALL METHOD lo_object->(lv_method)
PARAMETER-TABLE lt_params
EXCEPTION-TABLE lt_results.
WRITE: / 'Résultat:', lv_output.

9. Analyser les méthodes et attributs de classe

DATA: lv_classname TYPE seoclsname VALUE 'CL_ABAP_TYPEDESCR'.
" Charger la description de classe
DATA(lo_class) = CAST cl_abap_classdescr(
cl_abap_typedescr=>describe_by_name( lv_classname )
).
" Lister les méthodes
WRITE: / 'Méthodes:'.
LOOP AT lo_class->methods INTO DATA(ls_method).
WRITE: / '-', ls_method-name, ls_method-visibility.
ENDLOOP.
" Lister les attributs
WRITE: / 'Attributs:'.
LOOP AT lo_class->attributes INTO DATA(ls_attr).
WRITE: / '-', ls_attr-name, ls_attr-type_kind.
ENDLOOP.
" Interfaces
WRITE: / 'Interfaces:'.
LOOP AT lo_class->interfaces INTO DATA(ls_intf).
WRITE: / '-', ls_intf-name.
ENDLOOP.

10. Conversion générique de données

CLASS zcl_data_converter DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: convert_itab_to_string_table
IMPORTING it_data TYPE ANY TABLE
RETURNING VALUE(rt_result) TYPE string_table.
CLASS-METHODS: structure_to_json
IMPORTING is_data TYPE any
RETURNING VALUE(rv_json) TYPE string.
ENDCLASS.
CLASS zcl_data_converter IMPLEMENTATION.
METHOD convert_itab_to_string_table.
" Analyser la structure
DATA(lo_table) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_data )
).
DATA(lo_struct) = CAST cl_abap_structdescr(
lo_table->get_table_line_type( )
).
" Convertir chaque ligne en string
LOOP AT it_data ASSIGNING FIELD-SYMBOL(<fs_row>).
DATA(lv_line) = ``.
LOOP AT lo_struct->components INTO DATA(ls_comp).
ASSIGN COMPONENT ls_comp-name OF STRUCTURE <fs_row>
TO FIELD-SYMBOL(<fv_value>).
IF lv_line IS NOT INITIAL.
lv_line = lv_line && `;`.
ENDIF.
lv_line = lv_line && <fv_value>.
ENDLOOP.
APPEND lv_line TO rt_result.
ENDLOOP.
ENDMETHOD.
METHOD structure_to_json.
" Conversion JSON simple
DATA(lo_struct) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_data( is_data )
).
rv_json = `{`.
DATA(lv_first) = abap_true.
LOOP AT lo_struct->components INTO DATA(ls_comp).
ASSIGN COMPONENT ls_comp-name OF STRUCTURE is_data
TO FIELD-SYMBOL(<fv_value>).
IF lv_first = abap_false.
rv_json = rv_json && `,`.
ENDIF.
lv_first = abap_false.
rv_json = rv_json && |"{ to_lower( ls_comp-name ) }":"|.
rv_json = rv_json && <fv_value>.
rv_json = rv_json && `"`.
ENDLOOP.
rv_json = rv_json && `}`.
ENDMETHOD.
ENDCLASS.

11. Créer une clause WHERE dynamique

CLASS zcl_where_builder DEFINITION.
PUBLIC SECTION.
METHODS: add_condition
IMPORTING iv_field TYPE string
iv_operator TYPE string
iv_value TYPE any
RETURNING VALUE(ro_self) TYPE REF TO zcl_where_builder.
METHODS: add_range
IMPORTING iv_field TYPE string
it_range TYPE ANY TABLE
RETURNING VALUE(ro_self) TYPE REF TO zcl_where_builder.
METHODS: get_where
RETURNING VALUE(rv_where) TYPE string.
PRIVATE SECTION.
DATA: mt_conditions TYPE string_table.
ENDCLASS.
CLASS zcl_where_builder IMPLEMENTATION.
METHOD add_condition.
DATA(lv_condition) = |{ iv_field } { iv_operator } '{ iv_value }'|.
APPEND lv_condition TO mt_conditions.
ro_self = me.
ENDMETHOD.
METHOD add_range.
DATA: lv_condition TYPE string.
LOOP AT it_range ASSIGNING FIELD-SYMBOL(<fs_range>).
ASSIGN COMPONENT 'SIGN' OF STRUCTURE <fs_range> TO FIELD-SYMBOL(<fv_sign>).
ASSIGN COMPONENT 'OPTION' OF STRUCTURE <fs_range> TO FIELD-SYMBOL(<fv_option>).
ASSIGN COMPONENT 'LOW' OF STRUCTURE <fs_range> TO FIELD-SYMBOL(<fv_low>).
ASSIGN COMPONENT 'HIGH' OF STRUCTURE <fs_range> TO FIELD-SYMBOL(<fv_high>).
CASE <fv_option>.
WHEN 'EQ'.
lv_condition = |{ iv_field } = '{ <fv_low> }'|.
WHEN 'BT'.
lv_condition = |{ iv_field } BETWEEN '{ <fv_low> }' AND '{ <fv_high> }'|.
WHEN 'CP'.
lv_condition = |{ iv_field } LIKE '{ <fv_low> }'|.
ENDCASE.
IF <fv_sign> = 'E'.
lv_condition = |NOT ( { lv_condition } )|.
ENDIF.
APPEND lv_condition TO mt_conditions.
ENDLOOP.
ro_self = me.
ENDMETHOD.
METHOD get_where.
rv_where = concat_lines_of( table = mt_conditions sep = ` AND ` ).
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lo_where) = NEW zcl_where_builder( ).
DATA(lv_where) = lo_where->add_condition(
iv_field = 'LAND1"
iv_operator = '="
iv_value = 'FR"
)->add_condition(
iv_field = 'KTOKD"
iv_operator = '="
iv_value = '0001"
)->get_where( ).
" Résultat : LAND1 = 'FR' AND KTOKD = '0001"

12. Copie générique de table

CLASS zcl_table_copy DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: copy_matching_fields
IMPORTING it_source TYPE ANY TABLE
CHANGING ct_target TYPE ANY TABLE.
ENDCLASS.
CLASS zcl_table_copy IMPLEMENTATION.
METHOD copy_matching_fields.
" Analyser la structure cible
DATA(lo_target_table) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( ct_target )
).
DATA(lo_target_struct) = CAST cl_abap_structdescr(
lo_target_table->get_table_line_type( )
).
" Analyser la structure source
DATA(lo_source_table) = CAST cl_abap_tabledescr(
cl_abap_typedescr=>describe_by_data( it_source )
).
DATA(lo_source_struct) = CAST cl_abap_structdescr(
lo_source_table->get_table_line_type( )
).
" Déterminer les champs correspondants
DATA(lt_matching) = VALUE string_table( ).
LOOP AT lo_target_struct->components INTO DATA(ls_target_comp).
READ TABLE lo_source_struct->components
WITH KEY name = ls_target_comp-name
TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
APPEND ls_target_comp-name TO lt_matching.
ENDIF.
ENDLOOP.
" Copier les données
DATA: lr_target_line TYPE REF TO data.
CREATE DATA lr_target_line LIKE LINE OF ct_target.
ASSIGN lr_target_line->* TO FIELD-SYMBOL(<fs_target_line>).
LOOP AT it_source ASSIGNING FIELD-SYMBOL(<fs_source_line>).
CLEAR <fs_target_line>.
LOOP AT lt_matching INTO DATA(lv_field).
ASSIGN COMPONENT lv_field OF STRUCTURE <fs_source_line>
TO FIELD-SYMBOL(<fv_source>).
ASSIGN COMPONENT lv_field OF STRUCTURE <fs_target_line>
TO FIELD-SYMBOL(<fv_target>).
<fv_target> = <fv_source>.
ENDLOOP.
INSERT <fs_target_line> INTO TABLE ct_target.
ENDLOOP.
ENDMETHOD.
ENDCLASS.

13. Appels de modules fonctions dynamiques

DATA: lv_funcname TYPE funcname VALUE 'BAPI_CUSTOMER_GETLIST',
lt_params TYPE abap_func_parmbind_tab,
lt_excps TYPE abap_func_excpbind_tab.
" Préparer les paramètres
DATA: lt_addressdata TYPE bapi1006_address_t,
lt_return TYPE bapiret2_t.
lt_params = VALUE #(
( name = 'ADDRESSDATA"
kind = abap_func_tables
value = REF #( lt_addressdata ) )
( name = 'RETURN"
kind = abap_func_tables
value = REF #( lt_return ) )
).
lt_excps = VALUE #(
( name = 'OTHERS' value = 99 )
).
" Appel dynamique
CALL FUNCTION lv_funcname
PARAMETER-TABLE lt_params
EXCEPTION-TABLE lt_excps.
IF sy-subrc = 0.
" Succès
ENDIF.

14. RTTS pour les CDS Views

" Analyser la structure d'une CDS View
DATA(lv_cds_view) = 'ZI_CUSTOMER'.
DATA(lo_cds_type) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_name( lv_cds_view )
).
" Afficher les champs
LOOP AT lo_cds_type->components INTO DATA(ls_cds_comp).
WRITE: / ls_cds_comp-name, ls_cds_comp-type_kind.
ENDLOOP.
" SELECT dynamique sur CDS View
DATA: lr_cds_data TYPE REF TO data.
DATA(lo_cds_table) = cl_abap_tabledescr=>create( lo_cds_type ).
CREATE DATA lr_cds_data TYPE HANDLE lo_cds_table.
ASSIGN lr_cds_data->* TO FIELD-SYMBOL(<ft_cds_data>).
SELECT * FROM (lv_cds_view)
INTO TABLE @<ft_cds_data>
UP TO 100 ROWS.

15. Générateur de rapport générique

CLASS zcl_generic_report DEFINITION.
PUBLIC SECTION.
METHODS: generate_report
IMPORTING iv_table TYPE tabname
it_fields TYPE string_table
iv_where TYPE string OPTIONAL
iv_max_rows TYPE i DEFAULT 1000
RETURNING VALUE(rt_output) TYPE string_table.
ENDCLASS.
CLASS zcl_generic_report IMPLEMENTATION.
METHOD generate_report.
DATA: lr_data TYPE REF TO data.
" Créer une table dynamique
DATA(lo_struct) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_name( iv_table )
).
" Seulement les champs souhaités
DATA(lt_components) = VALUE cl_abap_structdescr=>component_table( ).
LOOP AT it_fields INTO DATA(lv_field).
DATA(lo_comp_type) = lo_struct->get_component_type( to_upper( lv_field ) ).
IF lo_comp_type IS BOUND.
APPEND VALUE #(
name = to_upper( lv_field )
type = lo_comp_type
) TO lt_components.
ENDIF.
ENDLOOP.
IF lt_components IS INITIAL.
RETURN.
ENDIF.
DATA(lo_new_struct) = cl_abap_structdescr=>create( lt_components ).
DATA(lo_new_table) = cl_abap_tabledescr=>create( lo_new_struct ).
CREATE DATA lr_data TYPE HANDLE lo_new_table.
ASSIGN lr_data->* TO FIELD-SYMBOL(<ft_data>).
" Lire les données
DATA(lv_fields) = concat_lines_of( table = it_fields sep = ` ` ).
IF iv_where IS INITIAL.
SELECT (lv_fields) FROM (iv_table)
INTO CORRESPONDING FIELDS OF TABLE @<ft_data>
UP TO @iv_max_rows ROWS.
ELSE.
SELECT (lv_fields) FROM (iv_table)
WHERE (iv_where)
INTO CORRESPONDING FIELDS OF TABLE @<ft_data>
UP TO @iv_max_rows ROWS.
ENDIF.
" Créer l'en-tête
DATA(lv_header) = concat_lines_of( table = it_fields sep = `|` ).
APPEND lv_header TO rt_output.
" Lignes de données
LOOP AT <ft_data> ASSIGNING FIELD-SYMBOL(<fs_row>).
DATA(lv_line) = ``.
LOOP AT lt_components INTO DATA(ls_comp).
ASSIGN COMPONENT ls_comp-name OF STRUCTURE <fs_row>
TO FIELD-SYMBOL(<fv_value>).
IF lv_line IS NOT INITIAL.
lv_line = lv_line && `|`.
ENDIF.
lv_line = lv_line && <fv_value>.
ENDLOOP.
APPEND lv_line TO rt_output.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
" Utilisation
DATA(lt_report) = NEW zcl_generic_report( )->generate_report(
iv_table = 'KNA1"
it_fields = VALUE #( ( `KUNNR` ) ( `NAME1` ) ( `ORT01` ) ( `LAND1` ) )
iv_where = 'LAND1 = ''FR''"
).
LOOP AT lt_report INTO DATA(lv_line).
WRITE: / lv_line.
ENDLOOP.

Remarques importantes / Bonnes pratiques

  • RTTS pour les informations de type à l’exécution.
  • CREATE DATA TYPE HANDLE pour les objets de données dynamiques.
  • ASSIGN COMPONENT pour l’accès aux champs dans les structures dynamiques.
  • Casting avec CAST pour la conversion de type des descripteurs.
  • Performance à considérer – le code dynamique est plus lent.
  • Gestion des erreurs pour les types/champs inexistants.
  • Injection SQL à éviter dans les clauses WHERE dynamiques.
  • Appels de méthodes dynamiques avec PARAMETER-TABLE.
  • Combinez avec Field Symbols pour un traitement efficace.
  • Utilisez CDS Views pour des définitions type-safe.