Dynamic structures and data references have been well-documented and written for more than a decade already and it’s one of the most flexible (albeit, also the most dangerous security-wise) components of SAP development. I would not be re-engineering the wheel but presenting it in different layers of a dynamic structure.
In addition, I wouldn’t include the inline declarations yet as it defeats the purpose of dynamism in the context of learning.
We’ll try to peel the onion and go from the simplest to the more complex structures using dynamic and data references. What I would try is to best explain dynamic programming to someone who is new to ABAP programming as this is a huge topic and learning it early helps develop a good framework mindset.
For those who want to create tools in the future, you must have a strong base in dynamic programming.
Disclaimer: The examples below are just simple representations and not actual codes in the system.
Level 1. Dynamic variable. Access any variable in SAP ABAP
Strong Warning: This is highly inadvisable. This is also dangerous when used incorrectly
Usage: If you wanted to access variables in the call stack, particularly on user exits or enhancements.
With great power comes great responsibility. Use it only when necessary and avoid as much as possible.
FIELD-SYMBOLS: <lfs_vkorg> TYPE vkorg.
CONSTANTS: lc_curr_vkorg TYPE objectname VALUE '(SAPMV13H)KOMGH-VKORG'.
... Do something ...
ASSIGN (lc_curr_vkorg) TO <lfs_vkorg>.
IF <lfs_vkorg> IS ASSIGNED.
IF <lfs_vkorg> IS NOT INITIAL.
... Do something ...
ENDIF.
ENDIF.
Level 2. Dynamic Structure
Usage: This would usually go hand in hand with Dynamic tables but if not, it gives you an opportunity to create a structure out of a name or a reference (like another variable). There are multiple ways to create a dynamic structure but one of them is below
PARAMETERS: p_tab TYPE tabname.
DATA: lo_structure TYPE REF TO cl_abap_structdescr,
lr_data TYPE REF TO data.
lo_structure ?= cl_abap_structdescr=>describe_by_name( p_name = p_tab ).
CREATE DATA lr_data TYPE HANDLE lo_structure.
ASSIGN lr_data->* TO FIELD-SYMBOL(<lfs_structure>).
IF <lfs_structure> IS ASSIGNED.
" Do something
ENDIF.
Another example is a dynamically generated structure without a “reference”. In this example, I am generating a structure with a number of standard price field based on the parameter p_cnt. It generates the structure on runtime. This is very useful in programs where it can change the output.
One common use case is generating an ALV output based on a time range like a monthly value where the number of months is variable depending on user output.
PARAMETERS: p_cnt TYPE i.
TYPES: BEGIN OF ty_material_price,
matnr TYPE matnr,
waers TYPE waers,
END OF ty_material_price.
DATA: go_struc TYPE REF TO cl_abap_structdescr,
go_new_struc TYPE REF TO cl_abap_structdescr,
gr_structure TYPE REF TO data,
go_tabdata TYPE REF TO data,
go_descr TYPE REF TO cl_abap_datadescr,
gv_price TYPE STPRS,
gs_mat_price TYPE ty_material_price.
go_struc ?= cl_abap_structdescr=>describe_by_data( gs_mat_price ).
DATA(components) = go_struc->get_components( ).
DO p_cnt TIMES.
go_descr ?= cl_abap_typedescr=>describe_by_data( gv_price ).
APPEND INITIAL LINE TO components
ASSIGNING FIELD-SYMBOL(<component>).
<component> = VALUE #( name = |STPRS{ sy-index }| type = go_descr ).
ENDDO.
TRY.
DATA(new_struc) = cl_abap_structdescr=>create( p_components = components ). " Structure Type Object
CREATE DATA gr_structure TYPE HANDLE new_struc.
ASSIGN gr_structure->* TO FIELD-SYMBOL(<new_structure>).
IF <new_structure> IS ASSIGNED.
" Do something
ENDIF.
CATCH cx_sy_struct_creation. " Exception when creating a structure description
ENDTRY.
Level 3. Dynamic Table
Most of the usage of dynamism in ABAP would revolve in dynamic tables. There are countless usages, particularly in dynamic programs that revolve in the creation of dynamic table. In fact, most framework programs would have some concept of dynamic tables in the coding.
Similar to dynamic structure, there are multiple ways to create a dynamic table but just to continue with the dynamic structure to dynamic table creation, below is a good example of how to create one.
PARAMETERS: p_tab TYPE tabname.
DATA: lo_structure TYPE REF TO cl_abap_structdescr,
lo_table TYPE REF TO cl_abap_tabledescr,
lr_data TYPE REF TO data,
lr_table_data TYPE REF TO data.
FIELD-SYMBOLS: <lfs_table> TYPE STANDARD TABLE.
lo_structure ?= cl_abap_structdescr=>describe_by_name( p_name = p_tab ).
CREATE DATA lr_data TYPE HANDLE lo_structure.
lo_table ?= cl_abap_tabledescr=>create(
p_line_type = lo_structure " Line Type
* p_table_kind = tablekind_std " Table Category (STANDARD, SORTED, HASHED)
* p_unique = abap_false " Uniqueness of the Key
* p_key = " Key table
* p_key_kind = keydefkind_default " Key category
).
CREATE DATA lr_table_data TYPE HANDLE lo_table.
ASSIGN lr_data->* TO FIELD-SYMBOL(<lfs_structure>).
ASSIGN lr_table_data->* TO FIELD-SYMBOL(<lfs_table_data>).
IF <lfs_table_data> IS ASSIGNED.
" Do something
ENDIF.
Following the dynamic structure example #2, we can also use the structure to create an internal table out of the dynamic structure.
PARAMETERS: p_cnt TYPE i.
TYPES: BEGIN OF ty_material_price,
matnr TYPE matnr,
waers TYPE waers,
END OF ty_material_price.
DATA: go_struc TYPE REF TO cl_abap_structdescr,
go_new_struc TYPE REF TO cl_abap_structdescr,
gr_structure TYPE REF TO data,
gr_table TYPE REF TO data,
go_descr TYPE REF TO cl_abap_datadescr,
gv_price TYPE STPRS,
gs_mat_price TYPE ty_material_price.
FIELD-SYMBOLS: <new_table> TYPE STANDARD TABLE.
go_struc ?= cl_abap_structdescr=>describe_by_data( gs_mat_price ).
DATA(components) = go_struc->get_components( ).
DO p_cnt TIMES.
go_descr ?= cl_abap_typedescr=>describe_by_data( gv_price ).
APPEND INITIAL LINE TO components
ASSIGNING FIELD-SYMBOL(<component>).
<component> = VALUE #( name = |STPRS{ sy-index }| type = go_descr ).
ENDDO.
TRY.
DATA(new_struc) = cl_abap_structdescr=>create( p_components = components ). " Structure Type Object
CREATE DATA gr_structure TYPE HANDLE new_struc.
ASSIGN gr_structure->* TO FIELD-SYMBOL(<new_structure>).
IF <new_structure> IS ASSIGNED.
" Do something
ENDIF.
DATA(new_table) = cl_abap_tabledescr=>create( p_line_type = new_struc ).
CREATE DATA gr_table TYPE HANDLE new_table.
ASSIGN gr_table->* TO <new_table>.
IF <new_table> IS ASSIGNED.
" Do something
ENDIF.
CATCH cx_sy_table_creation. " Exception when Creating a Table Type
CATCH cx_sy_struct_creation. " Exception when creating a structure description
ENDTRY.
Level 4. Accessing dynamic fields in the dynamic structure
The entire dynamic table is useless if you cannot access the fields in the structure. The fields from dynamic structures and tables are ambiguous on design time and can only be generated in run time.
That said, you can access them using the ASSIGN COMPONENT command or the ASSIGN command. It will try to access the field or component in run time. Both achieves the same result.
CREATE DATA gr_structure TYPE HANDLE new_struc.
ASSIGN gr_structure->* TO FIELD-SYMBOL(<new_structure>).
IF <new_structure> IS ASSIGNED.
gv_field = |STPRS{ p_cnt }|.
"Option 1
ASSIGN <new_structure>-(gv_field) TO FIELD-SYMBOL(<stprs2>).
IF <stprs2> IS ASSIGNED.
<stprs2> = 10.
ENDIF.
"Option 2
ASSIGN COMPONENT gv_field OF STRUCTURE <new_structure> TO FIELD-SYMBOL(<stprs>).
IF <stprs> IS ASSIGNED.
<stprs> = 20.
" Do something
BREAK-POINT.
ENDIF.
UNASSIGN <stprs>.
" Do something
ENDIF.
Level 5. Dynamic SELECT statement
Continuing with the theme of the dynamic table, we can select dynamically and create dynamic tables. This can also be achieved differently but this is basically one example on how to achieve a dynamic select statement.
Strong Warning: While this is very useful, make sure that you secure your code for any injections by adding authorization checks on your dynamic programs.
PARAMETERS: p_tab TYPE tabname,
p_matnr TYPE matnr.
TYPES: BEGIN OF ty_list,
name TYPE tabname,
r_str TYPE REF TO data,
r_tbl TYPE REF TO data,
END OF ty_list.
DATA: lt_list TYPE STANDARD TABLE OF ty_list.
DATA: lo_structure TYPE REF TO cl_abap_structdescr,
lo_table TYPE REF TO cl_abap_tabledescr,
lt_fields TYPE STANDARD TABLE OF fieldname,
lt_CONDTAB TYPE STANDARD TABLE OF hrcond,
lt_where TYPE STANDARD TABLE OF string,
lr_data TYPE REF TO data,
lr_table_data TYPE REF TO data.
FIELD-SYMBOLS: <lfs_table> TYPE STANDARD TABLE.
lo_structure ?= cl_abap_structdescr=>describe_by_name( p_name = p_tab ).
CREATE DATA lr_data TYPE HANDLE lo_structure.
lo_table ?= cl_abap_tabledescr=>create(
p_line_type = lo_structure " Line Type
* p_table_kind = tablekind_std " Table Category (STANDARD, SORTED, HASHED)
* p_unique = abap_false " Uniqueness of the Key
* p_key = " Key table
* p_key_kind = keydefkind_default " Key category
).
CREATE DATA lr_table_data TYPE HANDLE lo_table.
ASSIGN lr_data->* TO FIELD-SYMBOL(<lfs_structure>).
ASSIGN lr_table_data->* TO FIELD-SYMBOL(<lfs_table_data>).
lt_condtab = VALUE #( ( field = 'MATNR' opera = 'EQ' low = p_matnr ) ).
IF <lfs_table_data> IS ASSIGNED.
IF p_tab EQ 'MARA'.
lt_fields = VALUE #( ( 'MATNR' ) ).
CALL FUNCTION 'RH_DYNAMIC_WHERE_BUILD'
EXPORTING
dbtable = p_Tab
TABLES
condtab = lt_condtab
where_clause = lt_where
EXCEPTIONS
empty_condtab = 1
no_db_field = 2
unknown_db = 3
wrong_condition = 4
OTHERS = 5.
SELECT (lt_fields)
FROM (p_tab)
WHERE (lt_where)
INTO CORRESPONDING FIELDS OF TABLE @<lfs_table_data>.
" Do something
ENDIF.
ENDIF.
Level 6. Reference Variables (DATA)
As you would have noticed, this has been used in the above examples already. There’s no limit on the creation of dynamic objects with the usage of data references.
For example, you can create an internal table with data references. In the sample below, you can create a typed internal table with data references to dynamic structures and tables.
TYPES: BEGIN OF ty_list,
name TYPE tabname,
r_str TYPE REF TO data,
r_tbl TYPE REF TO data,
END OF ty_list.
DATA: lt_list TYPE STANDARD TABLE OF ty_list.
Data references can also be passed via methods or function modules with a well-structured reference variable. If you really need to call dynamic data, the best approach is via Reference variables.
PARAMETERS: p_tab TYPE tabname.
CLASS lcl_main DEFINITION.
PUBLIC SECTION.
CLASS-METHODS: set_data CHANGING o_tbl TYPE REF TO data.
ENDCLASS.
TYPES: BEGIN OF ty_list,
name TYPE tabname,
r_str TYPE REF TO data,
r_tbl TYPE REF TO data,
END OF ty_list.
DATA: lt_list TYPE STANDARD TABLE OF ty_list.
DATA: lo_structure TYPE REF TO cl_abap_structdescr,
lo_table TYPE REF TO cl_abap_tabledescr,
lr_data TYPE REF TO data,
lr_table_data TYPE REF TO data.
FIELD-SYMBOLS: <lfs_table> TYPE STANDARD TABLE.
lo_structure ?= cl_abap_structdescr=>describe_by_name( p_name = p_tab ).
CREATE DATA lr_data TYPE HANDLE lo_structure.
lo_table ?= cl_abap_tabledescr=>create(
p_line_type = lo_structure " Line Type
* p_table_kind = tablekind_std " Table Category (STANDARD, SORTED, HASHED)
* p_unique = abap_false " Uniqueness of the Key
* p_key = " Key table
* p_key_kind = keydefkind_default " Key category
).
CREATE DATA lr_table_data TYPE HANDLE lo_table.
ASSIGN lr_data->* TO FIELD-SYMBOL(<lfs_structure>).
ASSIGN lr_table_data->* TO FIELD-SYMBOL(<lfs_table_data>).
IF <lfs_table_data> IS ASSIGNED.
lcl_main=>set_data(
CHANGING
o_tbl = lr_table_data
).
ENDIF.
CLASS lcl_main IMPLEMENTATION.
METHOD set_data.
FIELD-SYMBOLS: <lfs_data> TYPE STANDARD TABLE.
ASSIGN o_tbl->* TO <lfs_data>.
IF <lfs_data> IS ASSIGNED.
" Do something
ENDIF.
ENDMETHOD.
ENDCLASS.
Level 7. Dynamic Methods
One of the best ways to teach a new developer ABAP OO concepts, particularly polymorphism in some aspects, is the usage of Class Interfaces.
A good example of its usage is when you wanted to run different logic depending in configuration. For example, you have a program that can either send to excel or rest service or to flat file in the local server while making it configurable, the best practice way to standardize is the usage of Class Interfaces.
DATA: lo_adapter TYPE REF TO zif_adapter,
ls_data TYPE ty_data.
IF ms_adapter-s_adapter-call_class IS NOT INITIAL.
TRY.
CREATE OBJECT lo_adapter TYPE (ms_adapter-s_adapter-call_class).
IF lo_adapter IS BOUND.
lo_adapter->send( CHANGING is_data = ls_data ).
ENDIF.
CATCH cx_sy_dyn_call_illegal_method.
CATCH cx_sy_create_object_error.
ENDTRY.
ENDIF.
Classes with interfaces
" Class #1. Send via Email
CLASS zcl_email definition
public
final
create public .
PUBLIC SECTION.
INTERFACES: zif_adapter.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD zif_adapter~send.
" Send via email
ENDMETHOD.
ENDCLASS.
" Class #2. Send via FTP
CLASS zcl_ftp definition
public
final
create public .
PUBLIC SECTION.
INTERFACES: zif_adapter.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS lcl_main IMPLEMENTATION.
METHOD zif_adapter~send.
" Send to ftp server
ENDMETHOD.
ENDCLASS.
No comments:
Post a Comment