Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Escaping OOP boundaries

Escaping OOP boundaries

Most of developers are constantly teaching and discussing modern patterns, frameworks and libraries. But what if you think that you know almost everything about traditional OOP in PHP? I can bet that at some level of mastery you could notice that traditional object-oriented patterns do not solve all problems but introduce new questions instead. This is because OOP-way is not suited well for complex tasks.

Are you looking for the new food for thoughts about how such complex issues could be solved in PHP? For example how to make your existing method asynchronous just with one single word? Or how to reduce boilerplate code for feature toggles?

Join me and I will show you how to apply the most powerful aspect-oriented framework to escape from your existing OOP boundaries and teach you new patterns that can help you to keep your code clean.

Alexander Lisachenko

November 09, 2019
Tweet

More Decks by Alexander Lisachenko

Other Decks in Programming

Transcript

  1. Escaping from OOP boundaries
    Alexander Lisachenko

    View Slide

  2. About me:
    2
    lisachenko
    lisachenko

    View Slide

  3. About me:
    ‣ Head of Web Development and
    Architecture at Alpari (RU) Forex Broker
    2
    lisachenko
    lisachenko

    View Slide

  4. About me:
    ‣ Head of Web Development and
    Architecture at Alpari (RU) Forex Broker
    ‣ Have worked with computers since 7
    years
    2
    lisachenko
    lisachenko

    View Slide

  5. About me:
    ‣ Head of Web Development and
    Architecture at Alpari (RU) Forex Broker
    ‣ Have worked with computers since 7
    years
    ‣ Clean code advocate, guru in the
    Enterprise Architecture
    2
    lisachenko
    lisachenko

    View Slide

  6. About me:
    ‣ Head of Web Development and
    Architecture at Alpari (RU) Forex Broker
    ‣ Have worked with computers since 7
    years
    ‣ Clean code advocate, guru in the
    Enterprise Architecture
    ‣ Author of the aspect-oriented
    framework Go! AOP 

    http://go.aopphp.com
    2
    lisachenko
    lisachenko

    View Slide

  7. 3
    Moscow PHP User Group

    View Slide

  8. 3
    Moscow PHP User Group

    View Slide

  9. Agenda:
    4

    View Slide

  10. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    4

    View Slide

  11. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    ‣ Escaping from PHP boundaries.
    4

    View Slide

  12. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    ‣ Escaping from PHP boundaries.
    ‣ Stream wrapper and filters.
    4

    View Slide

  13. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    ‣ Escaping from PHP boundaries.
    ‣ Stream wrapper and filters.
    ‣ Cross-Cutting concerns.
    4

    View Slide

  14. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    ‣ Escaping from PHP boundaries.
    ‣ Stream wrapper and filters.
    ‣ Cross-Cutting concerns.
    ‣ Aspect-Oriented Programming with Go! AOP
    4

    View Slide

  15. Agenda:
    ‣ Object-Oriented Paradigm boundaries
    ‣ Escaping from PHP boundaries.
    ‣ Stream wrapper and filters.
    ‣ Cross-Cutting concerns.
    ‣ Aspect-Oriented Programming with Go! AOP
    ‣ Hacking the PHP. Native structures and
    opcodes.
    4

    View Slide

  16. 5
    OOP Boundaries

    View Slide

  17. 6
    Boundary 1: Open - Closed Principle

    View Slide

  18. 6
    Boundary 1: Open - Closed Principle

    View Slide

  19. Class is Open for extension, but
    Closed for modification
    7

    View Slide

  20. 8
    Final class prevents inheritance

    View Slide

  21. 8
    Final class prevents inheritance

    View Slide

  22. 9
    Final class prevents inheritance

    View Slide

  23. 9
    Final class prevents inheritance

    View Slide

  24. 9
    Final class prevents inheritance
    Fatal error: Class Child may not inherit from final class (Secret)

    View Slide

  25. 10
    Final method prevents overriding

    View Slide

  26. 10
    Final method prevents overriding

    View Slide

  27. 11
    Final method prevents overriding

    View Slide

  28. 11
    Final method prevents overriding

    View Slide

  29. 11
    Final method prevents overriding
    Fatal error: Cannot override final method Secret::test()

    View Slide

  30. 11
    Final method prevents overriding
    Fatal error: Cannot override final method Secret::test()

    View Slide

  31. 12
    Private modifier restricts access

    View Slide

  32. 12
    Private modifier restricts access

    View Slide

  33. 13
    Private modifier restricts access

    View Slide

  34. 13
    Private modifier restricts access

    View Slide

  35. 14
    Private modifier restricts access
    Uncaught Error: Call to private method Secret::test()

    View Slide

  36. 15
    Boundary 2: Immutable PHP structures

    View Slide

  37. 15
    Boundary 2: Immutable PHP structures

    View Slide

  38. 16
    PHP

    View Slide

  39. 16
    PHP
    Interpreter?

    View Slide

  40. 16
    PHP
    Interpreter? Compiler?

    View Slide

  41. 16
    PHP
    Interpreter? Compiler?
    BOTH!

    View Slide

  42. 17
    PHP Script

    View Slide

  43. 17
    PHP Script
    Execute

    View Slide

  44. 17
    PHP Script
    Parse
    Execute

    View Slide

  45. 17
    PHP Script
    Parse
    Execute
    Compile to OpCodes

    View Slide

  46. 17
    PHP Script
    Parse
    Execute
    Compile to OpCodes
    Execute(ZendEngine)

    View Slide

  47. 17
    PHP Script
    Parse
    Execute
    Compile to OpCodes
    Execute(ZendEngine)
    Output

    View Slide

  48. 17
    PHP Script
    Execute
    Execute(ZendEngine)
    Output
    Load OpCache <- Opcodes

    View Slide

  49. Once compiled into Opcodes, code can not be
    changed in runtime
    18

    View Slide

  50. 19
    OpCache:

    View Slide

  51. 19
    OpCache:
    +Fixed-size shared memory-block

    View Slide

  52. 19
    OpCache:
    +Fixed-size shared memory-block
    +Contains PHP binary structures

    View Slide

  53. 19
    OpCache:
    +Fixed-size shared memory-block
    +Contains PHP binary structures
    +Structures are immutable

    View Slide

  54. 19
    OpCache:
    +Fixed-size shared memory-block
    +Contains PHP binary structures
    +Structures are immutable
    +Append-only

    View Slide

  55. 20
    OpCache shared immutable data:

    View Slide

  56. 20
    OpCache shared immutable data:
    +File functions (HashTable)

    View Slide

  57. 20
    OpCache shared immutable data:
    +File functions (HashTable)
    +File classes (HashTable)

    View Slide

  58. 20
    OpCache shared immutable data:
    +File functions (HashTable)
    +File classes (HashTable)
    +File main OpArray

    View Slide

  59. 20
    OpCache shared immutable data:
    +File functions (HashTable)
    +File classes (HashTable)
    +File main OpArray
    +Interned strings and immutable arrays

    View Slide

  60. 20
    OpCache shared immutable data:
    +File functions (HashTable)
    +File classes (HashTable)
    +File main OpArray
    +Interned strings and immutable arrays
    +Meta-information (system_id, etc)

    View Slide

  61. Shared immutable OpCache improves PHP
    performance a lot
    21

    View Slide

  62. 22
    OpCache OOP restrictions:

    View Slide

  63. 22
    OpCache OOP restrictions:
    - Method could not be added/deleted or
    changed in runtime

    View Slide

  64. 22
    OpCache OOP restrictions:
    - Method could not be added/deleted or
    changed in runtime
    - Class, its parent, implemented interfaces and
    used traits are declared in compile-time,
    runtime API is not available

    View Slide

  65. 22
    OpCache OOP restrictions:
    - Method could not be added/deleted or
    changed in runtime
    - Class, its parent, implemented interfaces and
    used traits are declared in compile-time,
    runtime API is not available
    - Low-level access to the memory is restricted
    to prevents errors and memory leaks

    View Slide

  66. 22
    PS. Possible only with extensions like runkit, uopz, etc
    OpCache OOP restrictions:
    - Method could not be added/deleted or
    changed in runtime
    - Class, its parent, implemented interfaces and
    used traits are declared in compile-time,
    runtime API is not available
    - Low-level access to the memory is restricted
    to prevents errors and memory leaks

    View Slide

  67. 23
    Escaping from OOP Boundaries

    View Slide

  68. Сan we mock/extend a final class or
    override a final method?
    24

    View Slide

  69. Сan we mock/extend a final class or
    override a final method?
    24
    Yes?

    View Slide

  70. Сan we mock/extend a final class or
    override a final method?
    24
    Yes? No?

    View Slide

  71. The simplest solution is to not mark classes or
    methods as final!
    25

    View Slide

  72. 26
    Escape: uopz extension

    View Slide

  73. «uopz is a black magic extension
    of the runkit-and-scary-stuff genre,
    intended to help with QA infrastructure.
    27
    Joe Watkins

    View Slide

  74. 28
    Mocking a final class - uopz extension

    View Slide

  75. 28
    Mocking a final class - uopz extension

    View Slide

  76. 28
    Mocking a final class - uopz extension

    View Slide

  77. 29
    Escape: Replace the «file» stream wrapper

    View Slide

  78. 30
    Idea:

    View Slide

  79. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes

    View Slide

  80. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)

    View Slide

  81. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)
    ‣ Register our own file handler via
    stream_wrapper_register(‘file’, self::class)

    View Slide

  82. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)
    ‣ Register our own file handler via
    stream_wrapper_register(‘file’, self::class)
    ‣ Can hook into the PHP source file now!

    View Slide

  83. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)
    ‣ Register our own file handler via
    stream_wrapper_register(‘file’, self::class)
    ‣ Can hook into the PHP source file now!
    ‣ Tokenise code with token_get_all($source)

    View Slide

  84. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)
    ‣ Register our own file handler via
    stream_wrapper_register(‘file’, self::class)
    ‣ Can hook into the PHP source file now!
    ‣ Tokenise code with token_get_all($source)
    ‣ Transform this source (eg. remove final keyword)

    View Slide

  85. 30
    Idea:
    ‣ PHP uses internal «file» wrapper for includes
    ‣ We can unregister the «file» handler via
    stream_wrapper_unregister(‘file’)
    ‣ Register our own file handler via
    stream_wrapper_register(‘file’, self::class)
    ‣ Can hook into the PHP source file now!
    ‣ Tokenise code with token_get_all($source)
    ‣ Transform this source (eg. remove final keyword)
    ‣ Give it back to the PHP to compile

    View Slide

  86. 31
    dg/bypass-finals library
    composer show dg/bypass-finals
    name : dg/bypass-finals
    descrip. : Removes final keyword from source code on-the-
    fly and allows mocking of final methods and classes
    keywords : testing, phpunit, unit, mocking, finals
    versions : dev-master, v1.1.2, v1.1.1, v1.1.0, v1.0.1, v1.0
    type : library

    View Slide

  87. 32
    Usage:

    View Slide

  88. 32
    Usage:
    You need to enable it before the classes you want to
    remove the final are loaded. So call it as soon as
    possible!

    View Slide

  89. 32
    Usage:
    You need to enable it before the classes you want to
    remove the final are loaded. So call it as soon as
    possible!
    PS. For additional details about PhpUnit integration, please read:
    https://www.tomasvotruba.cz/blog/2019/03/28/how-to-mock-final-classes-in-phpunit/

    View Slide

  90. 33
    antecedent/patchwork library
    composer show antecedent/patchwork
    name : antecedent/patchwork
    descrip. : Method redefinition (monkey-patching)
    functionality for PHP.
    keywords : aop, testing, aspect, runkit, redefinition,
    monkeypatching, interception
    versions : dev-master, 2.1.11, 2.1.10…
    type : library

    View Slide

  91. Write tests for your PHP code,
    not code for the tests.
    34

    View Slide

  92. 35
    Escape: Use PHP stream filters with includes

    View Slide

  93. 36
    Simple test file:

    View Slide

  94. 36
    Simple test file:

    View Slide

  95. 37
    Let’s craft our special include:

    View Slide

  96. 37
    Let’s craft our special include:

    View Slide

  97. 38
    It is become upper-cased:

    View Slide

  98. 38
    It is become upper-cased:

    View Slide

  99. Idea: we can register our own stream filter via
    stream_filter_register()
    to process a source code.
    39

    View Slide

  100. Cross-cutting concerns

    View Slide

  101. Cross-cutting concerns
    Data layer
    Service layer
    Controller layer

    View Slide

  102. Cross-cutting concerns
    Data layer
    Service layer
    Controller layer
    Authorization
    Logging
    Caching
    Transaction Control

    View Slide

  103. Cross-cutting concerns are aspects of a
    program that affect other domains.
    41

    View Slide

  104. Why?
    42

    View Slide

  105. Limitation of object-oriented
    representation of real life.
    43

    View Slide

  106. Joe Armstrong
    “…You wanted a banana but what
    you got was a gorilla holding the
    banana and the entire jungle”
    44

    View Slide

  107. Joe Armstrong
    “…You wanted a banana but what
    you got was a gorilla holding the
    banana and the entire jungle”
    44

    View Slide

  108. Example:
    45

    View Slide

  109. Example:
    45
    Implement an audit system that
    checks an access rights for each
    public method in all classes in our
    system and then logs this
    information into the security journal.

    View Slide

  110. Example:
    46

    View Slide

  111. Example:
    46

    View Slide

  112. Authorization:
    47

    View Slide

  113. Authorization:
    47

    View Slide

  114. Logging and audit:
    48

    View Slide

  115. Logging and audit:
    48

    View Slide

  116. Error handling
    49

    View Slide

  117. Error handling
    49

    View Slide

  118. Code tangling
    50

    View Slide

  119. Code tangling
    50

    View Slide

  120. Code tangling
    50

    View Slide

  121. Code scattering (duplication)
    51

    View Slide

  122. Code scattering (duplication)
    51

    View Slide

  123. Code scattering (duplication)
    51

    View Slide

  124. Cross-cutting concerns
    52

    View Slide

  125. 53
    Cross-cutting concerns

    View Slide

  126. Aspect-Oriented Paradigm

    View Slide

  127. Idea of AOP: extract cross-cutting concerns
    from general classes into the separate entities
    called aspects.
    55

    View Slide

  128. Authorization concern:
    56

    View Slide

  129. Authorization concern:
    56

    View Slide

  130. Authorization aspect:
    57

    View Slide

  131. Authorization aspect:
    57

    View Slide

  132. Authorization aspect:
    57
    Advice - event handler

    View Slide

  133. Authorization aspect:
    57
    Pointcut - describes list of
    interesting events
    Advice - event handler

    View Slide

  134. Authorization aspect:
    57
    Pointcut - describes list of
    interesting events
    Advice - event handler
    Joinpoint - defines an event
    object

    View Slide

  135. 58
    Aspect VS Event Listener

    View Slide

  136. 58
    Aspect VS Event Listener

    View Slide

  137. 58
    Aspect VS Event Listener

    View Slide

  138. 58
    Aspect VS Event Listener

    View Slide

  139. 59
    Advice types

    View Slide

  140. 59
    Advice types
    «Before» type

    View Slide

  141. 59
    Advice types
    «Before» type
    «After» type

    View Slide

  142. 59
    Advice types
    «Before» type
    «After» type
    «Around» type

    View Slide

  143. With AOP we can subscribe to any method
    without changing original code.
    60

    View Slide

  144. 61
    goaop/framework
    composer show goaop/framework
    name : goaop/framework
    descrip. : Framework for aspect-oriented programming in PHP.
    keywords : php, aop, library, aspect
    versions : 3.0.x-dev, 2.3.2,…
    type : library
    https://github.com/goaop/framework

    View Slide

  145. How it works?
    62

    View Slide

  146. How it works?
    62

    View Slide

  147. 63
    We use our trick with php://filter stream filter

    View Slide

  148. 64
    The composer class loader is replaced with a weaving
    proxy

    View Slide

  149. 65
    Lexical analysis and parsing of source code into the AST is
    performed
    (nikic/PHP-Parser)

    View Slide

  150. 66
    Static reflection is created from the AST
    (goaop/parser-reflection)

    View Slide

  151. 66
    Static reflection is created from the AST
    (goaop/parser-reflection)

    View Slide

  152. 66
    Static reflection is created from the AST
    (goaop/parser-reflection)

    View Slide

  153. 66
    Static reflection is created from the AST
    (goaop/parser-reflection)

    View Slide

  154. 67
    The original class is renamed and replaced with a new class
    with additional behavior; stored in the cache

    View Slide

  155. 67
    The original class is renamed and replaced with a new class
    with additional behavior; stored in the cache

    View Slide

  156. 67
    The original class is renamed and replaced with a new class
    with additional behavior; stored in the cache
    Same class name!

    View Slide

  157. 67
    The original class is renamed and replaced with a new class
    with additional behavior; stored in the cache
    Original class renamed
    Same class name!

    View Slide

  158. 67
    The original class is renamed and replaced with a new class
    with additional behavior; stored in the cache
    Original class renamed
    Overridden method
    Same class name!

    View Slide

  159. 68
    Deferred methods
    Runtime transformation of methods into deferred methods

    View Slide

  160. 68
    Deferred methods
    Runtime transformation of methods into deferred methods

    View Slide

  161. 69
    Example code

    View Slide

  162. 69
    Example code
    Some long service call

    View Slide

  163. 69
    Example code

    View Slide

  164. 70
    Idea: prevent execution of methods
    wrapping them into promises and run
    after the fastcgi_finish_request.

    View Slide

  165. 71
    Define an annotation-marker

    View Slide

  166. 72
    Define an aspect

    View Slide

  167. 72
    Define an aspect

    View Slide

  168. 72
    Define an aspect
    All methods with «Async» annotation

    View Slide

  169. 72
    Define an aspect
    Record each call with arguments

    View Slide

  170. 72
    Define an aspect
    Prevent execution of original method

    View Slide

  171. 72
    Define an aspect
    Return «our» value

    View Slide

  172. 73
    Define an aspect

    View Slide

  173. 73
    Define an aspect

    View Slide

  174. 73
    Define an aspect

    View Slide

  175. 73
    Define an aspect

    View Slide

  176. 73
    Define an aspect
    Finish FASTCGI Request

    View Slide

  177. 73
    Define an aspect
    Execute delayed methods

    View Slide

  178. 74
    Declare method as deferred

    View Slide

  179. 74
    Declare method as deferred

    View Slide

  180. 74
    Declare method as deferred
    Now it will be deferred

    View Slide

  181. Hacking the PHP engine

    View Slide

  182. Hacking the PHP engine

    View Slide

  183. View Slide

  184. 77
    FFI - Foreign Function Interface

    View Slide

  185. 77
    ‣ Available from PHP>=7.4
    FFI - Foreign Function Interface

    View Slide

  186. 77
    ‣ Available from PHP>=7.4
    ‣ Allows calling C functions
    FFI - Foreign Function Interface

    View Slide

  187. 77
    ‣ Available from PHP>=7.4
    ‣ Allows calling C functions
    ‣ Allows using C data types from pure scripting
    language
    FFI - Foreign Function Interface

    View Slide

  188. 77
    ‣ Available from PHP>=7.4
    ‣ Allows calling C functions
    ‣ Allows using C data types from pure scripting
    language
    ‣ Opens a way to write PHP extensions and
    bindings to C libraries in pure PHP
    FFI - Foreign Function Interface

    View Slide

  189. 77
    ‣ Available from PHP>=7.4
    ‣ Allows calling C functions
    ‣ Allows using C data types from pure scripting
    language
    ‣ Opens a way to write PHP extensions and
    bindings to C libraries in pure PHP
    ‣ Headers can be preloaded during server startup
    FFI - Foreign Function Interface

    View Slide

  190. 78
    FFI - Foreign Function Interface

    View Slide

  191. Idea: what if we use PHP’s FFI to access…
    79

    View Slide

  192. Idea: what if we use PHP’s FFI to access…
    79
    … PHP itself!

    View Slide

  193. Idea: what if we use PHP’s FFI to access…
    79
    … PHP itself!

    View Slide

  194. 80
    lisachenko/z-engine
    composer show lisachenko/z-engine
    name : lisachenko/z-engine
    descrip. : Library that provides direct access to native PHP
    structures.
    versions : dev-master, 0.5.0,…
    type : library
    https://github.com/lisachenko/z-engine

    View Slide

  195. 81
    lisachenko/z-engine

    View Slide

  196. 81
    ‣ Build on top of PHP’s Reflection API, eg
    ReflectionClass, ReflectionMethod
    lisachenko/z-engine

    View Slide

  197. 81
    ‣ Build on top of PHP’s Reflection API, eg
    ReflectionClass, ReflectionMethod
    ‣ Provides low-level binding to PHP’s structures
    like zval, zend_class_entry, zend_function
    and much more…
    lisachenko/z-engine

    View Slide

  198. 81
    ‣ Build on top of PHP’s Reflection API, eg
    ReflectionClass, ReflectionMethod
    ‣ Provides low-level binding to PHP’s structures
    like zval, zend_class_entry, zend_function
    and much more…
    ‣ Manipulates PHP itself in runtime
    lisachenko/z-engine

    View Slide

  199. 81
    ‣ Build on top of PHP’s Reflection API, eg
    ReflectionClass, ReflectionMethod
    ‣ Provides low-level binding to PHP’s structures
    like zval, zend_class_entry, zend_function
    and much more…
    ‣ Manipulates PHP itself in runtime
    ‣ Expect memory leaks and segfaults!
    lisachenko/z-engine

    View Slide

  200. 82
    Un-final class

    View Slide

  201. 82
    Un-final class

    View Slide

  202. 82
    Un-final class

    View Slide

  203. 82
    Un-final class

    View Slide

  204. 83
    Serializable Closure class

    View Slide

  205. 83
    Serializable Closure class

    View Slide

  206. 84
    Serializable Closure class

    View Slide

  207. 84
    Serializable Closure class

    View Slide

  208. 85
    Custom Throwable objects

    View Slide

  209. 86
    Custom Throwable objects

    View Slide

  210. 86
    Custom Throwable objects

    View Slide

  211. 87
    Native assembly methods in PHP

    View Slide

  212. 87
    Native assembly methods in PHP

    View Slide

  213. 88
    First ever assembly method in PHP

    View Slide

  214. 89
    What to expect:

    View Slide

  215. 89
    ‣ API for userland PHP extensions
    What to expect:

    View Slide

  216. 89
    ‣ API for userland PHP extensions
    ‣ OpCode manipulation and transformation
    What to expect:

    View Slide

  217. 89
    ‣ API for userland PHP extensions
    ‣ OpCode manipulation and transformation
    ‣ Shared objects (will survive between requests)
    What to expect:

    View Slide

  218. 89
    ‣ API for userland PHP extensions
    ‣ OpCode manipulation and transformation
    ‣ Shared objects (will survive between requests)
    ‣ Native hooks for PHP callbacks
    What to expect:

    View Slide

  219. 89
    ‣ API for userland PHP extensions
    ‣ OpCode manipulation and transformation
    ‣ Shared objects (will survive between requests)
    ‣ Native hooks for PHP callbacks
    ‣ Much more :)
    What to expect:

    View Slide

  220. View Slide

  221. View Slide

  222. Thank you for the attention!
    https://github.com/goaop
    https://github.com/lisachenko
    https://twitter.com/lisachenko
    Leave feedback at: https://joind.in/talk/310fd

    View Slide

  223. Go! AOP: Plugin for the PhpStorm
    92

    View Slide

  224. Pointcut syntax highlighting,
    completion and analysis
    93

    View Slide

  225. Pointcut syntax highlighting,
    completion and analysis
    93

    View Slide

  226. Navigate to advice/advised
    elements
    94

    View Slide

  227. Navigate to advice/advised
    elements
    94

    View Slide