Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make export of objects customizable #56

Closed
wants to merge 5 commits into from

Conversation

sebastianbergmann
Copy link
Owner

Copy link

codecov bot commented Mar 29, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (cb81155) to head (f8faf8a).
Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##                main       #56   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity        49        60   +11     
===========================================
  Files              1         2    +1     
  Lines            159       179   +20     
===========================================
+ Hits             159       179   +20     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

src/Exporter.php Outdated Show resolved Hide resolved
@staabm
Copy link
Contributor

staabm commented Mar 29, 2024

lgtm

src/Exporter.php Outdated Show resolved Hide resolved
@sebastianbergmann sebastianbergmann deleted the customizable-object-export branch March 31, 2024 06:52
$buffer = $this->defaultObjectExport($value, $processed, $indentation);
}

return $class . ' Object #' . spl_object_id($value) . ' (' . $buffer . ')';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more thing I just noticed.

the $class . ' Object #' prefix will only be present in "root objects". in custom object exporters this $class . ' Object #' format needs to be replicated, even if the custom exporter delegates export back to the built-in one.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we deal with that in Exporter::exportObject() or should we trust implementors of custom object exporters to do the right thing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a way how Exporter::exportObject() could do that in a way which would even work when custom exporters would delegate exporting of some stuff back to the exporter object.

Maybe we just need a test-case so we can see whether it works already or what a custom ObjectExporter needs todo to get it right

Copy link
Contributor

@staabm staabm Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the problem would be less theoretic if we try to implement a real world exporter

Maybe @BladeMF could try his use-case..?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that ... and would rather not make a release without this feature having been validated through real world use cases.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BladeMF ping

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BladeMF Can you help us?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, missed it in April. Let me have a look.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we can try some pseudo-code first and see where that takes us, OK?

So, if I was to write a Doctrine entity exporter the case might be something like the following. A typical entity object graph might look like this (the notation is (<class-name>)#<instance-name> for classes and then <property-type> $<property-name>: <property-value>, the below is taken from a recent object but simplified):

(Folder)#1 {
  int $id: 4,
  string $name: "Folder 1",
  array|(ArrayAccess&Countable&Traversable) $visibleToEmployees: (PersistentCollection)#2 {
    (listing the items below)
    (Employee)#3 { ... }
    (Employee)#4 { ... }
    (Employee)#5 { ... }
  }
  array|(ArrayAccess&Countable&Traversable) $editableByEmployees: (PersistentCollection)#6 {
    _listing the items below_
    (Employee)#3 { ... } (this instance appears a second time)
    (Employee)#6 { ... }
    (Employee)#7 { ... }
  }
}

so the exporter code would be something like this:

public function export(mixed $object, Exporter $defaultExporter, int $indentLevel): string
{
  // we assume we already know it's an entity
  $metadata = $this->metadataFactory->getMetadata($object);
  $indent = str_repeat("  ", $indent);
  $indentPlus1 = str_repeat("  ", $indent + 1);
  $lines = [];
  foreach($metadata->properties as $property) {
    $value = $property->getValue($object);
    if($value instanceof Collection) {
      // it's a Doctrine collection
      $objectLines = [];
      foreach($value as $item){
        // see coment below about calling the default exporter
        $objectLines [] = $indentPlus1. $defaultExporter->export($value, $indent + 1); 
      }
      $lines[] = $indent . $defaultExporter->formatExport($value, implode("\n". $indent, $objectLines));
      continue;
    }

    // Use the defaut export, it will call me back to check if I support it if is an entity
    // I could check it here and do a shortcut, but this is way more cleaner:
    // if it's an entity, I get the list of properties from the repository (as opposed to all class properties)
    // and then handle the collections.
    // There also could be another exporter with greater priority than me, 
    // so it's not really legal for me to jump the line
    $lines[] = $defaultExporter->export($value, $indent + 1); 
  }
  return "\n".implode("\n". $indent, $lines).
}

the formatExport function might look something as silly as:

function formatExport(object $object, string $buffer): string
{
  return $object::class . ' Object #' . spl_object_id($object) . ' (' . $buffer . ')';
}

It appears that a formatExport function is needed. I think the purpose of the custom exporter is just to export a specific class, nothing more, nothing less. It should defer anything else to the default exporter. I am not even sure the formatExport function is in the Exporter, it looks like a separate formatter class may be needed, but I won't die on that hill. I also kind of dislike the need for me to deal with the indent before the closing brace ), because it's mine-1, but I don't really know that, because the default formatter/exporter determines that, if that makes sense.

If the above (a bit messy) code is not convincing I could try and write one for real.

I am in the middle of moving to the seaside for 3 months, but I will try and think more about that code. It may very well be the case that I need to write one real.

sebastianbergmann added a commit that referenced this pull request Jun 17, 2024
sebastianbergmann added a commit that referenced this pull request Jun 17, 2024
This reverts commit d0d99db.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants