Drupal - Safe and elegant way to access nested entities via fields

Suggestion, create a domain object that has the logic.

Normally these entities represent something that fits your business-domain.

E.g in your instance, the node might be an event.

So you might model a domain object called EventWrapper.

<?php

namespace Drupal\my_domain;
use Drupal\node\NodeInterface;
use Drupal\media\MediaInterface;
use Drupal\file\FileInterface;

class EventWrapper {
  protected $node;
  public static function fromNode(NodeInterface $node): EventWrapper {
    $instance = new static();
    $instance->node = $node;
    return $instance;
  }
  public function getMedia() : ?MediaInterface {
    if ($this->node->hasField('field_media') && !$this->node->get('field_media')->isEmpty()) {
      return $this->node->field_media->entity;
    }
    return NULL;
  }
  public function getMediaImage() : ?FileInterface {
    if (($media = this->getMedia()) && $media->hasField('field_file') && !$media->get('field_file')->isEmpty()) {
      return $media->field_file->entity;
    }
    return NULL;
  }
  public function getImageCaption(): ?string {
    if (($file = this->getMediaImage()) && $file->hasField('field_text') && !$file->get('field_text')->isEmpty()) {
      return $file->field_text->value;
    }
    return NULL;
  }
}

Then in your code:

<?php

$image_caption = EventWrapper::fromNode($node)->getImageCaption();

Although normally you render paragraphs recursively, you can retrieve a fixed structure non-recursively by recreating it with foreach loops:

foreach ($node->field_paragraph->referencedEntities() as $paragraph) {
  foreach ($paragraph->field_media->referencedEntities() as $media) {
    ...
  }
}

This avoids accessing empty fields and is able to process multi-value fields.