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

ObjectManager's Factory should be replaceable depending on service #569

Closed
Ocramius opened this issue May 9, 2014 · 18 comments
Closed

ObjectManager's Factory should be replaceable depending on service #569

Ocramius opened this issue May 9, 2014 · 18 comments
Assignees

Comments

@Ocramius
Copy link

Ocramius commented May 9, 2014

Currently, the Magento\Framework\ObjectManager\ObjectManager accepts a single Magento\Framework\ObjectManager\Factory as a parameter

I've been trying to build better interceptors on top of ProxyManager, since the current Magento\Interception component is quite naive, and doesn't deal with a lot of corner-cases (different PHP and HHVM versions/bugs/gotchas).

The end aim is avoiding any kind of LSP violation (even for constructors) by having interceptors simply have their own factory type.

In pseudo-code, it would be something like this:

class InterceptorFactory extends Factory
{
    public function create($type, $arguments)
    {
        // either handled via inheritance or composition
        $realInstance = parent::create($type, $arguments);

        return $this->createInterceptorViaOtherLogic($realInstance);
    }
}

The ObjectManager would then need to use the configuration to find out the proper factory to be used with a particular service:

    public function create($type, array $arguments = array())
    {
        $factory = $this->_config->getFactory() ?: $this->_factory;
        return $factory->create($this->_config->getPreference($type), $arguments);
    }

This is just one possible approach, but it would make:

  • very complex services easier to instantiate
  • complete removal of the Interception component, plugging in a more robust solution
  • some overhead in ObjectManager#create() and ObjectManager#get()

I'm still digging through the files, but the main point is that instantiation logic is not always getting parameters from a parameter bag and stuffing them in a constructor, for example like in following pseudo-code:

public function create($objectManager)
{
    $object1 = $objectManager->get('object1');
    $object2 = $objectManager->get('object2');

    return $object1->createFrom($object2); // hard to do with the current ObjectManager
}

Maybe I missed the way that I can achieve this, but the current factory implementation seems quite inflexible.

@verklov verklov self-assigned this May 15, 2014
@verklov
Copy link
Contributor

verklov commented May 15, 2014

@Ocramius, thank you for your contribution! The team will analyze it and we will get back to you once we have the results.

@antonkril
Copy link
Contributor

@Ocramius Can you list the corner cases Magento\Framework\Interception doesn't cover?

As for constructors, they are not subject to LSP because they are not object methods and are not part of object's interface.

I'm not sure I understand the benefits of instantiation example you provided. We don't support such cases because it can be achieved with standard approaches. Also, $objectManager should not be used in module code directly, because we try to avoid service location.

@Ocramius
Copy link
Author

@antonkril mainly access to properties and non-public methods.

As for constructors, they are not subject to LSP because they are not object methods and are not part of object's interface.

Constructors are subject to the LSP as well. They may not be a part of the API in magento's core, but any usage of the constructor of an interceptor (usually done via get_class) may break your code. Yes, this is arguably wrong, but you can't possibly know how consumer code may use a passed in instance. The assumption of constructor injection only is also fairly bold ;-)

I'm not sure I understand the benefits of instantiation example you provided. We don't support such cases because it can be achieved with standard approaches.

The point here is lazy loading. If I want to skip instantiation of a service to make it lazy then I can't do that without overriding how it gets instantiated. More on this can be found here and here (I implemented it for both containers).

Also, $objectManager should not be used in module code directly, because we try to avoid service location.

Service location is perfectly acceptable in the context of factories. The locator doesn't need to leak outside the factory into services.

@otoolec
Copy link
Contributor

otoolec commented May 22, 2014

@Ocramius Have you seen the support for Proxy classes that already exists? They should help with the lazy loading, unless I'm missing some detail.

@antonkril
Copy link
Contributor

@Ocramius current implementation of Interception allows pluginization of protected methods conceptually. But this access was limited intentionally to prevent 3PD interaction with protected interface of classes in plugins, because it would be a hack, and would render any post-release magento code modifications backwards incompatible.

Constructors are subject to the LSP as well. They may not be a part of the API in magento's core, but any usage of the constructor of an interceptor (usually done via get_class) may break your code.

We avoid usages of get_class because it makes you depend on implementation instead of abstraction. Can you describe in which cases you want to interact with Interceptor constructor directly? We have ObjectManager that is responsible for instantiation. It abstracts 3PD from constructors. You should create your entities with factories, and services should be asked in constructor. So no direct interaction with either constructor or ObjectManager is required.

The point here is lazy loading.

You can read about lazy-loading in Magento with Proxies here

Service location is perfectly acceptable in the context of factories. The locator doesn't need to leak outside the factory into services.

100% agree. I didn't understand that example is from factory. But Magento 2 provides a way to do this without such custom logic.

@Ocramius
Copy link
Author

Do I get this correct when I say that you would explicitly override services with their proxies counterparts?

That's pretty much what I wanted to avoid, and instead silently override a service with a different instance (kind-of like a container compiling pass)

@antonkril
Copy link
Contributor

Different instance of a proxy is created, it substitutes expensive service, and when this service is required, proxy creates it. So this happens transparently to the client code. Client code doesn't distinguish if it works with a proxy or original object. Proxies are auto-generated.

@Ocramius
Copy link
Author

@antonkril that makes sense, though it seems that it needs manual overrides on the config (by explicitly using the proxy class instead of the real class). That can be fixed via some pre-compiling passes though.

Getting back to factories, I still don't see a way of using a different factory to build my object. Would something like a "factory" support be acceptable? I realize that this is touching very core components, but I would like to work on these before there's any feature freeze.

@antonkril
Copy link
Contributor

@antonkril that makes sense, though it seems that it needs manual overrides on the config (by explicitly using the proxy class instead of the real class). That can be fixed via some pre-compiling passes though.

Yes, you have to declare proxy in config. We had discussions about just declaring types or arguments as "lazy", but decided to leave it explicit for developers to understand the behavior. I'm not sure I understand, how pre-compiling will change the situation? Now we request minimal information in config.

Getting back to factories, I still don't see a way of using a different factory to build my object. Would something like a "factory" support be acceptable? IO

If you are talking about entity (non-injectable) factories, they are auto-generated, but if you create your own factory for your type and name it {your-classs-name\Factory}, it will be used instead of auto-generated. Of course your factory can contain any custom logic you want.

As for services or injectables (instances that you can ask for in constructor): for now we don't have factories for them. Proper object decomposition allows you to avoid factories for non-injectables. Introduction of such factories would add more "magic" and "invisible" code written by 3PD and would let them think less about their object decomposition.

That said, we still have discussions about these "service factories" and if you provide some valid use cases, we can revisit this.

@verklov
Copy link
Contributor

verklov commented Jun 13, 2014

@Ocramius, could you please provide use cases as asked by @antonkril?

@Ocramius
Copy link
Author

If you are talking about entity (non-injectable) factories, they are auto-generated, but if you create your own factory for your type and name it {your-classs-name\Factory}, it will be used instead of auto-generated. Of course your factory can contain any custom logic you want.

@antonkril is there also a way of using a factory without depending on a derived class name?

Proper object decomposition allows you to avoid factories for non-injectables.

It's not a perfect world, heh...

Introduction of such factories would add more "magic" and "invisible" code written by 3PD and would let them think less about their object decomposition

It would actually simplify instantiation for various kinds of services that don't just rely on constructor injection. That is not an issue for core itself, but for any dependency introduced in a project.

@Ocramius, could you please provide use cases as asked by @antonkril?

My particular use-case is about making services lazy/proxied (mainly for performance), as well as using own AOP-ish libraries. To do that, I'd basically need to swap out the default instantiation logic somehow, and defer it to a callable that I can either inject into the manager or pass in as a configuration value and that will be instantiated by the manager itself.

@antonkril
Copy link
Contributor

@antonkril is there also a way of using a factory without depending on a derived class name?

Sure.

It would actually simplify instantiation for various kinds of services that don't just rely on constructor injection. That is not an issue for core itself, but for any dependency introduced in a project.

Adapters should be used for that instead of factories. Adapters are also a better idea because they abstract you from specific implementation of the dependency.

My particular use-case is about making services lazy/proxied (mainly for performance)

Can you describle what are the problems of current implementation of lazy-loading and AOP?

@benmarks
Copy link
Contributor

@antonkril I just was speaking about this with @Ocramius at DPC. Happy toi chat more.

magento-team added a commit that referenced this issue Sep 26, 2014
* Various improvements:
   * Implemented a general way of using RSS module
   * Created a cron job in the Customer module for cleaning the customer_visitor table
   * Added a warning message to the Use HTTP Only option in the Admin panel
   * Implemented the Grid component in the Magento UI Library
   * Reimplemented the URL Rewrites functionality in the new UrlRedirect module
 * Framework improvements:
   * Added the ability to install Magento 2 using CLI
   * Aggregated Magento installation and upgrade into one tool
   * Refactored CustomerService REST WebApi to be more RESTful
   * Increased unit and integration test coverage
   * Moved page asset management to page configuration API, and eliminated the \Magento\Theme\Block\Html\Head block
   * Eliminated the Root, Html and Title blocks
 * Themes update:
   * Removed widgets from the default Magento installation
 * Fixed bugs:
   * Fixed an issue with wishlist creation for non-registered customer
   * Fixed an issue with Google Mapping where Condition did not show correct value
   * Fixed an issue  where there were too many notifications for admin user by default
   * Fixed a Daylight Savings Time calculation error
   * Fixed an issue where default cookie path and lifetime were not validated prior to saving
   * Fixed an issue where current admin password was not required for resetting admin password
   * Fixed an issue where custom customer attribute or customer address attribute was not accessible when custom_attribute is used as the attribute code
   * Fixed an issue where integration entity could not be deleted after being searched in grid
   * Fixed an issue where invalid parameter value was shown in SOAP
   * Fixed an issue where exception was thrown for Array to String conversion in SOAP
   * Fixed an issue where exception was thrown due to invalid argument supplied for foreach() statement in REST
   * Fixed an issue where admin tax notifications did not appear correctly in the System Messages dialog box
   * Fixed an issue where tax details were missing when viewing order in the Admin panel
   * Fixed an issue where styles for the storefront store selector were absent
   * Fixed an issue where customer got 404 page when switching store views on the product page of a product with different URL keys in different store views
   * Fixed an issue where the Add To Cart button in the MAP pop-up did not work for configurable and bundle products
   * Fixed an issue where for specifying options for configurable product was absent after adding a product from the MAP pop-up
   * Fixed an issue where a fatal error was thrown after selecting shipping method on PayPal Express Checkout
   * Fixed an issue with sending invoice email
   * Fixed an issue where integration tests failed with a fatal error
   * Fixed an issue where credit memo entry was not created after performing a refund for an order
   * Fixed an issue where categories layout for widgets did not work
   * Fixed an issue where opening a page restricted by ACL lead to blank page instead of the Access Denied page
   * Fixed an issue where a blank page was displayed instead of the using the Advanced Search result
   * Fixed an issue where the "Please wait" spinner was absent on Ajax requests for order creation in the Admin panel
   * Fixed an issue with the main navigation menu location on the page
 * Modularity:
   * Implemented the automatic applying of the MAP policy
 * Indexers:
   * Eliminated the old Magento_Index module
 * Search library
   * Added wildcards filter
   * Eliminated unused queries and filters
   * Added IN to Term filter
   * Moved the "value" attribute from <match> to <query> for the Match query
   * Refactored the usage of negation
   * Implemented Request Builder
 * CatalogSearch adapter
   * Pluginized adding attribute to search index
   * Merged base declaration with searchable attributes
 * Added the following Setup CLI tools in the setup folder
   * Deployment Configuration Tool
   * Schema Setup and Update Tool
   * DB Data Update Tool
   * Admin User Setup Tool
   * User Configuration Tool
   * Installation Tool
   * Update Tool
 * GitHub requests:
   * [#615] (#615) -- Use info as object in checkout_cart_update_items_before
   * [#659] (#659) -- Recently viewed products sidebar issue
   * [#660] (#660) -- RSS global setting
   * [#663] (#663) -- session.save_path not valid
   * [#445] (#445) -- use of registry in Magento\Tax\Helper\Data
   * [#646] (#646) -- Fixed flat category indexer bug
   * [#643] (#643) -- Configurable Products Performance
   * [#640] (#640) -- [Insight] Files should not be executable
   * [#667] (#667) -- Tiny improvement on render() method in Column/Renderer/Concat
   * [#288] (#288) -- Add Cell Phone to Customer Address Form
   * [#607] (#607) -- sitemap.xml filename is not variable
   * [#633] (#633) -- Fixed Typo ($_attribite -> $_attribute)
   * [#634] (#634) -- README.md contains broken link to X.commerce Agreement
   * [#569] (#569) -- ObjectManager's Factory should be replaceable depending on service
   * [#654] (#654) -- Demo notice overlapping
 * Functional tests:
   * Abandoned carts report
   * Adding products from wishlist to cart
   * Create invoice for offline payment methods
   * Delete products from shopping cart
   * Delete widget
   * Global search
   * Order count report
   * Order total report
@verklov
Copy link
Contributor

verklov commented Sep 26, 2014

The team has processed this issue. The updated code has just been deployed to the repository under version 0.1.0-alpha97. Thank you for your contribution! This issue is now closed.

@verklov verklov closed this as completed Sep 26, 2014
@Ocramius
Copy link
Author

@verklov thanks! Is there any sha1 reference that can be used to verify what was changed, related with this issue? :-)

@verklov
Copy link
Contributor

verklov commented Oct 21, 2014

@Ocramius, since we are performing deployments as a single merged commit, I guess there is only one way to see this. Please use this link https://github.com/magento/magento2/blame/873d38c884fb56411c4ac3bb46a9d4343e4545fe/lib/internal/Magento/Framework/App/ObjectManagerFactory.php#L105. The string that you are interested in appeared in version 0.1.0-alpha97.

@Ocramius
Copy link
Author

@verklov ok! Thanks for that =)

@verklov
Copy link
Contributor

verklov commented Oct 22, 2014

You are welcome! :-)

magento-team pushed a commit that referenced this issue Apr 27, 2016
okorshenko pushed a commit that referenced this issue Dec 14, 2016
…tory

MAGETWO-60477: Invalid SalesInventory module version - 2.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants